bug5828: added methods for role management to SecurityResource / SecuredServer facade

This commit is contained in:
Axel Uhl
2023-05-10 14:28:29 +02:00
parent 32b74d6bc7
commit 8991742298
13 changed files with 257 additions and 41 deletions
@@ -31,6 +31,7 @@ Export-Package: com.sap.sailing.landscape,
com.sap.sailing.landscape.impl,
com.sap.sailing.landscape.procedures
Bundle-ActivationPolicy: lazy
Import-Package: org.osgi.framework,
Import-Package: com.sap.sailing.server.security,
org.osgi.framework,
org.osgi.util.tracker;version="1.5.2"
Bundle-Activator: com.sap.sailing.landscape.impl.Activator
@@ -58,6 +58,7 @@ import com.sap.sailing.server.gateway.interfaces.CompareServersResult;
import com.sap.sailing.server.gateway.interfaces.MasterDataImportResult;
import com.sap.sailing.server.gateway.interfaces.SailingServer;
import com.sap.sailing.server.gateway.interfaces.SailingServerFactory;
import com.sap.sailing.server.security.EventManagerRole;
import com.sap.sse.ServerInfo;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
@@ -768,6 +769,13 @@ public class LandscapeServiceImpl implements LandscapeService {
securityServiceServer.addCurrentUserToGroup(userGroupId);
} else {
groupId = securityServiceServer.createUserGroupAndAddCurrentUser(serverGroupName);
try {
securityServiceServer.addRoleToUser(EventManagerRole.getInstance().getId(), securityServiceServer.getUsername(), null, null, /* transitive */ true);
} catch (Exception e) {
// this didn't work, but it's not the end of the world if we cannot grant the requesting user the
// event_manager:{group-name} role; the user may end up not having SERVER:CREATE_OBJECT...
logger.warning("Couldn't grant role "+" of user group "+serverGroupName+": "+e.getMessage());
}
try {
// try to set the group owner of the new group to the group itself, allowing all users with role user:{group-name} to
// change / edit it.
@@ -20,8 +20,9 @@ Require-Bundle: org.hamcrest;bundle-version="2.2.0",
com.sap.sse.security
Bundle-ClassPath: .
Automatic-Module-Name: com.sap.sailing.selenium.test
Import-Package: javax.ws.rs.core;version="1.1.1",
Import-Package: com.sap.sailing.server.security,
javax.ws.rs.core;version="1.1.1",
javax.xml.bind,
org.apache.http.client;version="4.5.5",
org.json.simple,
org.json.simple.parser,
javax.xml.bind
org.json.simple.parser
@@ -29,6 +29,7 @@ import com.sap.sailing.selenium.api.event.SecurityApi.AccessToken;
import com.sap.sailing.selenium.api.event.SecurityApi.Hello;
import com.sap.sailing.selenium.api.event.SecurityApi.User;
import com.sap.sailing.selenium.test.AbstractSeleniumTest;
import com.sap.sailing.server.security.EventManagerRole;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
@@ -37,6 +38,7 @@ import com.sap.sse.security.shared.WildcardPermission;
import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
import com.sap.sse.security.util.RemoteServerUtil;
import com.sap.sse.security.util.SecuredServer;
import com.sap.sse.security.util.SecuredServer.RoleDescriptor;
import com.sap.sse.security.util.impl.SecuredServerImpl;
public class SecurityApiTest extends AbstractSeleniumTest {
@@ -53,7 +55,7 @@ public class SecurityApiTest extends AbstractSeleniumTest {
@Test
public void testCreateAndGetUser() {
final ApiContext adminCtx = createAdminApiContext(getContextRoot(), SECURITY_CONTEXT);
final AccessToken createUserResponse = securityApi.createUser(adminCtx, "max", USERNAME_FULL, null, "start123");
final AccessToken createUserResponse = securityApi.createUser(adminCtx, USERNAME, USERNAME_FULL, null, "start123");
assertEquals("Responded username of createUser is different!", USERNAME, createUserResponse.getUsername());
assertNotNull("Token is missing in reponse!", createUserResponse.getAccessToken());
User getUserResponse = securityApi.getUser(adminCtx, USERNAME);
@@ -142,6 +144,19 @@ public class SecurityApiTest extends AbstractSeleniumTest {
}
}
@Test
public void testAddRoleToUser() throws ClientProtocolException, IOException, ParseException, IllegalAccessException {
final ApiContext adminCtx = createAdminApiContext(getContextRoot(), SECURITY_CONTEXT);
final SecuredServer securedServer = createSecuredServer(adminCtx);
securityApi.createUser(adminCtx, USERNAME, USERNAME_FULL, null, "start123");
securedServer.addRoleToUser(EventManagerRole.getInstance().getId(), USERNAME, /* qualified for group */ null, /* qualifiedForUserWithName */ USERNAME, /* transitive */ true);
final Iterable<RoleDescriptor> roles = securedServer.getRoles(USERNAME);
assertTrue(Util.stream(roles)
.anyMatch(r -> r.getQualifiedForGroupWithId() == null
&& r.getQualifiedForUserWithName().equals(USERNAME) && r.isTransitive()
&& r.getRoleDefinitionId().equals(EventManagerRole.getInstance().getId())));
}
@Test
public void testGetPermissions() throws ClientProtocolException, IOException, ParseException, IllegalAccessException {
final ApiContext adminCtx = createAdminApiContext(getContextRoot(), SECURITY_CONTEXT);
@@ -122,6 +122,4 @@ public interface SailingServer extends SecuredServer {
* removed as a result of calling this method
*/
RemoteSailingServerReference removeRemoteServerEventReferences(SailingServer referencedServer, Iterable<UUID> eventIds) throws Exception;
String getBearerToken();
}
@@ -92,6 +92,7 @@ public class UserImpl extends SecurityUserImpl<RoleDefinition, Role, UserGroup,
*
* @see #writeObject
* @see #readResolve
* @see #roleListForSerialization
*/
private transient Set<Role> roles;
@@ -448,8 +448,7 @@ public class UserManagementWriteServiceImpl extends UserManagementServiceImpl im
getOrThrowQualifiedUser(userQualifierName);
// get the group tenant the role is qualified for if one exists
final UserGroup tenant = getOrThrowTenant(tenantQualifierName);
final Role role = getOrThrowRoleFromIDsAndCheckMetaPermissions(roleDefinitionId, tenant == null ? null : tenant.getId(),
userQualifierName, transitive);
final Role role = getSecurityService().getOrThrowRoleFromIDsAndCheckMetaPermissions(roleDefinitionId, tenant == null ? null : tenant.getId(), userQualifierName, transitive);
final TypeRelativeObjectIdentifier associationTypeIdentifier = PermissionAndRoleAssociation.get(role, user);
final String message = "User "+SecurityUtils.getSubject().getPrincipal()+" added role " + role.getName() + " for user " + username;
getSecurityService().setOwnershipWithoutCheckPermissionForObjectCreationAndRevertOnError(
@@ -488,8 +487,7 @@ public class UserManagementWriteServiceImpl extends UserManagementServiceImpl im
getOrThrowQualifiedUser(userQualifierName);
// get the group tenant the role is qualified for if one exists
UserGroup tenant = getOrThrowTenant(tenantQualifierName);
Role role = getOrThrowRoleFromIDsAndCheckMetaPermissions(roleDefinitionId, tenant == null ? null : tenant.getId(),
userQualifierName, isTransitive);
Role role = getSecurityService().getOrThrowRoleFromIDsAndCheckMetaPermissions(roleDefinitionId, tenant == null ? null : tenant.getId(), userQualifierName, isTransitive);
final String message = SecurityUtils.getSubject().getPrincipal().toString()+" removed role " + role.getName() + " for user " + username;
final TypeRelativeObjectIdentifier associationTypeIdentifier = PermissionAndRoleAssociation.get(role, user);
final QualifiedObjectIdentifier qualifiedTypeIdentifier = SecuredSecurityTypes.ROLE_ASSOCIATION
@@ -625,21 +623,6 @@ public class UserManagementWriteServiceImpl extends UserManagementServiceImpl im
return user;
}
/**
* @return the role associated with the given IDs and qualifiers
* @throws UserManagementException
* if the current user does not have the meta permission to give this specific, qualified role in this
* context.
*/
protected Role getOrThrowRoleFromIDsAndCheckMetaPermissions(UUID roleDefinitionId, UUID tenantId, String userQualifierName, boolean transitive) throws UserManagementException {
final Role role = createRoleFromIDs(roleDefinitionId, tenantId, userQualifierName, transitive);
if (!getSecurityService().hasCurrentUserMetaPermissionsOfRoleDefinitionWithQualification(
role.getRoleDefinition(), role.getQualificationAsOwnership())) {
throw new UserManagementException("You are not allowed to take this role to the user.");
}
return role;
}
/**
* @return the user group associated with the tenantQualifierName
* @throws UserManagementException,
@@ -670,21 +653,6 @@ public class UserManagementWriteServiceImpl extends UserManagementServiceImpl im
return user;
}
protected Role createRoleFromIDs(UUID roleDefinitionId, UUID qualifyingTenantId, String qualifyingUsername, boolean transitive) throws UserManagementException {
final User user;
if (qualifyingUsername == null || qualifyingUsername.trim().isEmpty()) {
user = null;
} else {
user = getSecurityService().getUserByName(qualifyingUsername);
if (user == null) {
throw new UserManagementException("User "+qualifyingUsername+" not found for role qualification");
}
}
return new Role(
getSecurityService().getRoleDefinition(roleDefinitionId),
qualifyingTenantId == null ? null : getSecurityService().getUserGroup(qualifyingTenantId), user, transitive);
}
@Override
public AccessControlListDTO overrideAccessControlList(QualifiedObjectIdentifier idOfAccessControlledObject,
AccessControlListDTO acl) throws UnauthorizedException {
@@ -85,3 +85,5 @@ bearerTokenOrAnonymous = com.sap.sse.security.BearerTokenOrBasicOrFormOrAnonymou
/api/restsecurity/has_permission = bearerTokenOrAnonymous
/api/restsecurity/preferences/** = bearerToken
/api/restsecurity/users_with_permission = bearerToken
/api/restsecurity/get_roles_for_user = bearerToken
/api/restsecurity/add_role_to_user = bearerToken
@@ -790,8 +790,19 @@ public interface SecurityService extends ReplicableWithObjectInputStream<Replica
/**
* Updates the currently held SubscriptionPlanPrices for all known SubscriptionPlans
*
* @param itemPrices
*/
void updateSubscriptionPlanPrices(Map<String, BigDecimal> itemPrices);
Role createRoleFromIDs(UUID roleDefinitionId, UUID qualifyingTenantId, String qualifyingUsername, boolean transitive) throws UserManagementException;
/**
* @return the role associated with the given IDs and qualifiers
* @throws UserManagementException
* if the current user does not have the meta permission to give this specific, qualified role in this
* context.
*/
Role getOrThrowRoleFromIDsAndCheckMetaPermissions(UUID roleDefinitionId, UUID qualifyingGroupId, String userQualifierName,
boolean transitive) throws UserManagementException;
}
@@ -2983,4 +2983,46 @@ implements ReplicableSecurityService, ClearStateTestSupport {
return null;
}
@Override
public Role createRoleFromIDs(UUID roleDefinitionId, UUID qualifyingTenantId, String qualifyingUsername, boolean transitive) throws UserManagementException {
final User user;
if (qualifyingUsername == null || qualifyingUsername.trim().isEmpty()) {
user = null;
} else {
user = getUserByName(qualifyingUsername);
if (user == null) {
throw new UserManagementException("User "+qualifyingUsername+" not found for role qualification");
}
}
final UserGroup group;
if (qualifyingTenantId == null) {
group = null;
} else {
group = getUserGroup(qualifyingTenantId);
if (group == null) {
throw new UserManagementException("Group with ID "+qualifyingTenantId+" not found for role qualification");
}
}
final RoleDefinition roleDefinition = getRoleDefinition(roleDefinitionId);
if (roleDefinition == null) {
throw new UserManagementException("Role definition with ID "+roleDefinitionId+" not found");
}
return new Role(roleDefinition, group, user, transitive);
}
/**
* @return the role associated with the given IDs and qualifiers
* @throws UserManagementException
* if the current user does not have the meta permission to give this specific, qualified role in this
* context.
*/
@Override
public Role getOrThrowRoleFromIDsAndCheckMetaPermissions(UUID roleDefinitionId, UUID tenantId, String userQualifierName, boolean transitive) throws UserManagementException {
final Role role = createRoleFromIDs(roleDefinitionId, tenantId, userQualifierName, transitive);
if (!hasCurrentUserMetaPermissionsOfRoleDefinitionWithQualification(
role.getRoleDefinition(), role.getQualificationAsOwnership())) {
throw new UserManagementException("You are not allowed to take this role to the user.");
}
return role;
}
}
@@ -2,6 +2,7 @@ package com.sap.sse.security.jaxrs.api;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.logging.Logger;
@@ -28,12 +29,20 @@ import org.json.simple.JSONObject;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.mail.MailException;
import com.sap.sse.security.Action;
import com.sap.sse.security.SecurityUrlPathProvider;
import com.sap.sse.security.jaxrs.AbstractSecurityResource;
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
import com.sap.sse.security.shared.OwnershipAnnotation;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
import com.sap.sse.security.shared.UserManagementException;
import com.sap.sse.security.shared.WildcardPermission;
import com.sap.sse.security.shared.impl.PermissionAndRoleAssociation;
import com.sap.sse.security.shared.impl.Role;
import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
import com.sap.sse.security.shared.impl.User;
import com.sap.sse.security.shared.impl.UserGroup;
import com.sun.jersey.api.client.ClientResponse.Status;
@Path(SecurityResource.RESTSECURITY)
@@ -63,6 +72,15 @@ public class SecurityResource extends AbstractSecurityResource {
public static final String PERMISSION = "permission";
public static final String GRANTED = "granted";
public static final String ACCESS_TOKEN_METHOD = "/"+ACCESS_TOKEN;
public static final String ADD_ROLE_TO_USER_METHOD = "/add_role_to_user";
public static final String ROLE_DEFINITION_ID = "role_definition_id";
public static final String QUALIFYING_GROUP_ID = "qualifying_group_id";
public static final String QUALIFYING_USERNAME = "qualifying_username";
public static final String TRANSITIVE = "transitive";
public static final String GET_ROLES_FOR_USER_METHOD = "/get_roles_for_user";
public static final Object ROLE_NAME = "role_name";
public static final Object OWNING_GROUP_ID = "owning_group_id";
public static final Object OWNING_USER_NAME = "owning_user_name";
/**
* Can be used to figure out the current subject. Accepts the GET method. If the subject is
@@ -260,6 +278,84 @@ public class SecurityResource extends AbstractSecurityResource {
}
}
@PUT
@Path(ADD_ROLE_TO_USER_METHOD)
@Produces("text/plain;charset=UTF-8")
public Response addRoleToUser(@QueryParam(USERNAME) String username, @QueryParam(ROLE_DEFINITION_ID) String roleDefinitionId,
@QueryParam(QUALIFYING_GROUP_ID) String qualifyingGroupId, @QueryParam(QUALIFYING_USERNAME) String qualifyingUserName,
@QueryParam(TRANSITIVE) Boolean transitive) {
final Response response;
try {
// get user for which to add a role
final User user = getService().getUserByName(username);
if (user == null) {
response = Response.status(Status.NOT_FOUND).entity("User not found").build();
} else {
// get user for which the role is qualified, if one exists
final User qualifiedForUser = qualifyingUserName == null ? null : getService().getUserByName(qualifyingUserName);
if (qualifyingUserName != null && qualifiedForUser == null) {
response = Response.status(Status.NOT_FOUND).entity("Qualifying user not found").build();
} else {
// get the group tenant the role is qualified for if one exists
final UserGroup qualifyingGroup = qualifyingGroupId == null ? null : getService().getUserGroup(UUID.fromString(qualifyingGroupId));
if (qualifyingGroupId != null && qualifyingGroup == null) {
response = Response.status(Status.NOT_FOUND).entity("Qualifying group not found").build();
} else {
final Role role = getService().getOrThrowRoleFromIDsAndCheckMetaPermissions(
roleDefinitionId == null ? null : UUID.fromString(roleDefinitionId),
qualifyingGroup == null ? null : qualifyingGroup.getId(),
qualifiedForUser == null ? null : qualifiedForUser.getName(), transitive);
final TypeRelativeObjectIdentifier associationTypeIdentifier = PermissionAndRoleAssociation.get(role, user);
final String message = "User "+SecurityUtils.getSubject().getPrincipal()+" added role " + role.getName() + " for user " + username;
getService().setOwnershipWithoutCheckPermissionForObjectCreationAndRevertOnError(
SecuredSecurityTypes.ROLE_ASSOCIATION, associationTypeIdentifier,
associationTypeIdentifier.toString(), new Action() {
@Override
public void run() throws Exception {
final QualifiedObjectIdentifier qualifiedObjectAssociationIdentifier = SecuredSecurityTypes.ROLE_ASSOCIATION
.getQualifiedObjectIdentifier(associationTypeIdentifier);
getService().addToAccessControlList(qualifiedObjectAssociationIdentifier,
null, DefaultActions.READ.name());
getService().addRoleForUser(user, role);
logger.info(message);
}
});
response = Response.ok().build();
}
}
}
return response;
} catch (UserManagementException e) {
return Response.status(Status.PRECONDITION_FAILED).entity(e.getMessage()).build();
}
}
@GET
@Path(GET_ROLES_FOR_USER_METHOD)
@Produces("application/json;charset=UTF-8")
public Response getRoles(@QueryParam(USERNAME) String username) {
final JSONArray result = new JSONArray();
final User user = getService().getUserByName(username);
getService().checkCurrentUserReadPermission(user);
for (final Role role : user.getRoles()) {
final JSONObject roleJson = new JSONObject();
result.add(roleJson);
final TypeRelativeObjectIdentifier associationTypeIdentifier = PermissionAndRoleAssociation.get(role, user);
final QualifiedObjectIdentifier qualifiedObjectIdentifierForRoleAssociation = SecuredSecurityTypes.ROLE_ASSOCIATION.getQualifiedObjectIdentifier(associationTypeIdentifier);
if (getService().hasCurrentUserAnyPermission(qualifiedObjectIdentifierForRoleAssociation.getPermission(DefaultActions.READ))) {
final OwnershipAnnotation ownership = getService().getOwnership(qualifiedObjectIdentifierForRoleAssociation);
roleJson.put(ROLE_DEFINITION_ID, role.getRoleDefinition().getIdAsString());
roleJson.put(ROLE_NAME, role.getRoleDefinition().getName());
roleJson.put(OWNING_GROUP_ID, ownership == null ? null : ownership.getAnnotation() == null ? null : ownership.getAnnotation().getTenantOwner() == null ? null : ownership.getAnnotation().getTenantOwner().getId().toString());
roleJson.put(OWNING_USER_NAME, ownership == null ? null : ownership.getAnnotation() == null ? null : ownership.getAnnotation().getUserOwner() == null ? null : ownership.getAnnotation().getUserOwner().getName());
roleJson.put(QUALIFYING_GROUP_ID, role.getQualifiedForTenant() == null ? null : role.getQualifiedForTenant().getId().toString());
roleJson.put(QUALIFYING_USERNAME, role.getQualifiedForUser() == null ? null : role.getQualifiedForUser().getName());
roleJson.put(TRANSITIVE, role.isTransitive());
}
}
return Response.ok(streamingOutput(result)).build();
}
@PUT
@Path(USER_METHOD)
@Produces("text/plain;charset=UTF-8")
@@ -103,6 +103,22 @@ public interface SecuredServer {
Iterable<String> getNamesOfUsersInGroup(UUID userGroupId) throws ClientProtocolException, IOException, ParseException;
/**
* Adds the role identified by {@code roleId} to the user identified by {@code username} and makes it transitive or
* non-transitive based on the value of the {@code transitive} parameter.
*/
void addRoleToUser(UUID roleId, String username, UUID qualifiedForGroupWithId, String qualifiedForUserWithName,
boolean transitive) throws MalformedURLException, ClientProtocolException, IOException, ParseException;
public static interface RoleDescriptor {
UUID getRoleDefinitionId();
UUID getQualifiedForGroupWithId();
String getQualifiedForUserWithName();
Boolean isTransitive();
}
Iterable<RoleDescriptor> getRoles(String username) throws ClientProtocolException, IOException, ParseException;
/**
* Obtains the access control lists defined for the object identified by {@code type} and
* {@code typeRelativeObjectId}.
@@ -318,6 +318,63 @@ public class SecuredServerImpl implements SecuredServer {
return result;
}
@Override
public void addRoleToUser(UUID roleId, String username, UUID qualifiedForGroupWithId, String qualifiedForUserWithName, boolean transitive) throws ClientProtocolException, IOException, ParseException {
final URL addRoleToUserUrl = new URL(getBaseUrl(), SECURITY_API_PREFIX + SecurityResource.RESTSECURITY +
SecurityResource.ADD_ROLE_TO_USER_METHOD
+ "?" + SecurityResource.USERNAME+"="+username
+ "&" + SecurityResource.ROLE_DEFINITION_ID+"="+roleId.toString()
+ (qualifiedForGroupWithId == null ? "" : ("&" + SecurityResource.QUALIFYING_GROUP_ID+"="+qualifiedForGroupWithId.toString()))
+ (qualifiedForUserWithName == null ? "" : ("&" + SecurityResource.QUALIFYING_USERNAME+"="+qualifiedForUserWithName))
+ "&" + SecurityResource.TRANSITIVE+"="+transitive);
final HttpPut putRequest = new HttpPut(addRoleToUserUrl.toString());
final Pair<Object, Integer> result = getJsonParsedResponse(putRequest);
final Integer status = result.getB();
if (status == Response.Status.FORBIDDEN.getStatusCode() || status == Response.Status.UNAUTHORIZED.getStatusCode()) {
throw new AuthorizationException("Not allowed to add role with ID "+roleId+" to user "+username+": "+result.getA());
} else if (status < 200 || status >= 300) {
throw new IllegalArgumentException("Couldn't add role with ID "+roleId+ " to user "+username+": "+result.getA());
}
}
@Override
public Iterable<RoleDescriptor> getRoles(String username) throws ClientProtocolException, IOException, ParseException {
final URL getRolesForUserUrl = new URL(getBaseUrl(), SECURITY_API_PREFIX + SecurityResource.RESTSECURITY +
SecurityResource.GET_ROLES_FOR_USER_METHOD
+ "?" + SecurityResource.USERNAME+"="+username);
final HttpGet getRequest = new HttpGet(getRolesForUserUrl.toString());
final Pair<Object, Integer> result = getJsonParsedResponse(getRequest);
final Integer status = result.getB();
if (status == Response.Status.FORBIDDEN.getStatusCode() || status == Response.Status.UNAUTHORIZED.getStatusCode()) {
throw new AuthorizationException("Not allowed to get roles for user "+username+" to user "+username+": "+result.getA());
} else if (status < 200 || status >= 300) {
throw new IllegalArgumentException("Couldn't get roles from user "+username+": "+result.getA());
}
return Util.map((JSONArray) result.getA(), jsonRole->new RoleDescriptor() {
final JSONObject jsonRoleObject = (JSONObject) jsonRole;
@Override
public UUID getRoleDefinitionId() {
return UUID.fromString(jsonRoleObject.get(SecurityResource.ROLE_DEFINITION_ID).toString());
}
@Override
public UUID getQualifiedForGroupWithId() {
return jsonRoleObject.get(SecurityResource.QUALIFYING_GROUP_ID) == null ? null : UUID.fromString(jsonRoleObject.get(SecurityResource.QUALIFYING_GROUP_ID).toString());
}
@Override
public String getQualifiedForUserWithName() {
return jsonRoleObject.get(SecurityResource.QUALIFYING_USERNAME) == null ? null : jsonRoleObject.get(SecurityResource.QUALIFYING_USERNAME).toString();
}
@Override
public Boolean isTransitive() {
return jsonRoleObject.get(SecurityResource.TRANSITIVE) == null ? null : (Boolean) jsonRoleObject.get(SecurityResource.TRANSITIVE);
}
});
}
@Override
public String toString() {
return getBaseUrl().toString();