Ensure that /org/app/users/me always works for logged in user, regardless of roles/permissions, also that it is impossible to create an app or admin user named 'me'
Project: http://git-wip-us.apache.org/repos/asf/usergrid/repo Commit: http://git-wip-us.apache.org/repos/asf/usergrid/commit/c7c7c8cd Tree: http://git-wip-us.apache.org/repos/asf/usergrid/tree/c7c7c8cd Diff: http://git-wip-us.apache.org/repos/asf/usergrid/diff/c7c7c8cd Branch: refs/heads/usergrid-1268-akka-211 Commit: c7c7c8cd240a788b8cd3cf317590c23b9373385d Parents: 29e940e Author: Dave Johnson <snoopd...@apache.org> Authored: Mon May 9 14:58:18 2016 -0400 Committer: Dave Johnson <snoopd...@apache.org> Committed: Mon May 9 14:58:18 2016 -0400 ---------------------------------------------------------------------- .../rest/applications/users/UserResource.java | 9 ++ .../rest/applications/users/UsersResource.java | 9 ++ .../rest/management/users/UserResource.java | 4 + .../rest/management/users/UsersResource.java | 4 + .../security/SecuredResourceFilterFactory.java | 14 ++- .../usergrid/rest/applications/SecurityIT.java | 1 - .../collection/users/PermissionsResourceIT.java | 125 +++++++++++++++++-- .../test/resource/endpoints/NamedResource.java | 8 +- .../principals/ApplicationUserPrincipal.java | 13 +- 9 files changed, 165 insertions(+), 22 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/usergrid/blob/c7c7c8cd/stack/rest/src/main/java/org/apache/usergrid/rest/applications/users/UserResource.java ---------------------------------------------------------------------- diff --git a/stack/rest/src/main/java/org/apache/usergrid/rest/applications/users/UserResource.java b/stack/rest/src/main/java/org/apache/usergrid/rest/applications/users/UserResource.java index 373bf09..5435f7e 100644 --- a/stack/rest/src/main/java/org/apache/usergrid/rest/applications/users/UserResource.java +++ b/stack/rest/src/main/java/org/apache/usergrid/rest/applications/users/UserResource.java @@ -117,6 +117,11 @@ public class UserResource extends ServiceResource { Map<String, Object> json = mapper.readValue( body, mapTypeReference ); if ( json != null ) { + + if ( "me".equals( json.get("username") ) ) { + throw new IllegalArgumentException( "Username 'me' is reserved" ); + } + json.remove( "password" ); json.remove( "pin" ); } @@ -225,6 +230,10 @@ public class UserResource extends ServiceResource { return null; } + if ( "me".equals( json.get("username") ) ) { + throw new IllegalArgumentException( "Username 'me' is reserved" ); + } + ApiResponse response = createApiResponse(); response.setAction( "set user credentials" ); http://git-wip-us.apache.org/repos/asf/usergrid/blob/c7c7c8cd/stack/rest/src/main/java/org/apache/usergrid/rest/applications/users/UsersResource.java ---------------------------------------------------------------------- diff --git a/stack/rest/src/main/java/org/apache/usergrid/rest/applications/users/UsersResource.java b/stack/rest/src/main/java/org/apache/usergrid/rest/applications/users/UsersResource.java index 34c6915..247df8a 100644 --- a/stack/rest/src/main/java/org/apache/usergrid/rest/applications/users/UsersResource.java +++ b/stack/rest/src/main/java/org/apache/usergrid/rest/applications/users/UsersResource.java @@ -192,6 +192,10 @@ public class UsersResource extends ServiceResource { ObjectMapper mapper = new ObjectMapper(); Map<String, Object> json = mapper.readValue( body, mapTypeReference ); + if ( "me".equals( json.get("username") ) ) { + throw new IllegalArgumentException( "Username 'me' is reserved" ); + } + User user = getUser(); if ( user == null ) { return executePost( ui, body, callback ); @@ -233,6 +237,11 @@ public class UsersResource extends ServiceResource { if ( json instanceof Map ) { @SuppressWarnings("unchecked") Map<String, Object> map = ( Map<String, Object> ) json; + + if ( "me".equals( map.get("username") ) ) { + throw new IllegalArgumentException( "Username 'me' is reserved" ); + } + password = ( String ) map.get( "password" ); map.remove( "password" ); pin = ( String ) map.get( "pin" ); http://git-wip-us.apache.org/repos/asf/usergrid/blob/c7c7c8cd/stack/rest/src/main/java/org/apache/usergrid/rest/management/users/UserResource.java ---------------------------------------------------------------------- diff --git a/stack/rest/src/main/java/org/apache/usergrid/rest/management/users/UserResource.java b/stack/rest/src/main/java/org/apache/usergrid/rest/management/users/UserResource.java index ce79adf..28d46a6 100644 --- a/stack/rest/src/main/java/org/apache/usergrid/rest/management/users/UserResource.java +++ b/stack/rest/src/main/java/org/apache/usergrid/rest/management/users/UserResource.java @@ -111,6 +111,10 @@ public class UserResource extends AbstractContextResource { String username = string( json.remove( "username" ) ); String name = string( json.remove( "name" ) ); + if ( "me".equals( username ) ) { + throw new IllegalArgumentException( "Username 'me' is reserved" ); + } + management.updateAdminUser( user, username, name, email, json ); ApiResponse response = createApiResponse(); http://git-wip-us.apache.org/repos/asf/usergrid/blob/c7c7c8cd/stack/rest/src/main/java/org/apache/usergrid/rest/management/users/UsersResource.java ---------------------------------------------------------------------- diff --git a/stack/rest/src/main/java/org/apache/usergrid/rest/management/users/UsersResource.java b/stack/rest/src/main/java/org/apache/usergrid/rest/management/users/UsersResource.java index e57e5ef..ff279ef 100644 --- a/stack/rest/src/main/java/org/apache/usergrid/rest/management/users/UsersResource.java +++ b/stack/rest/src/main/java/org/apache/usergrid/rest/management/users/UsersResource.java @@ -127,6 +127,10 @@ public class UsersResource extends AbstractContextResource { throw new IllegalArgumentException( "email form parameter is required" ); } + if ( "me".equals( username ) ) { + throw new IllegalArgumentException( "Username 'me' is reserved" ); + } + // if username not provided, email will be used logger.info( "Create user: {}", (StringUtils.isNotBlank(username) ? username : email) ); http://git-wip-us.apache.org/repos/asf/usergrid/blob/c7c7c8cd/stack/rest/src/main/java/org/apache/usergrid/rest/security/SecuredResourceFilterFactory.java ---------------------------------------------------------------------- diff --git a/stack/rest/src/main/java/org/apache/usergrid/rest/security/SecuredResourceFilterFactory.java b/stack/rest/src/main/java/org/apache/usergrid/rest/security/SecuredResourceFilterFactory.java index 4c7d26a..67cf248 100644 --- a/stack/rest/src/main/java/org/apache/usergrid/rest/security/SecuredResourceFilterFactory.java +++ b/stack/rest/src/main/java/org/apache/usergrid/rest/security/SecuredResourceFilterFactory.java @@ -409,9 +409,10 @@ public class SecuredResourceFilterFactory implements DynamicFeature { logger.debug( "PathPermissionsFilter.authorize" ); } - final String PATH_MSG = - "---- Checked permissions for path --------------------------------------------\n" + "Requested path: {} \n" - + "Requested action: {} \n" + "Requested permission: {} \n" + "Permitted: {} \n"; + final String PATH_MSG = "---- Checked permissions for path --------------------------------------------\n" + + "Requested path: {} \n" + + "Requested action: {} \n" + "Requested permission: {} \n" + + "Permitted: {} \n"; ApplicationInfo application = null; @@ -449,6 +450,12 @@ public class SecuredResourceFilterFactory implements DynamicFeature { String path = request.getUriInfo().getPath().toLowerCase().replace(applicationName, ""); String perm = getPermissionFromPath( em.getApplicationRef().getUuid(), operation, path ); + if ( "/users/me".equals( path ) ) { + // shortcut the permissions checking, the "me" end-point is always allowed + logger.debug("Allowing {} access to /users/me", getSubject().toString() ); + return; + } + boolean permitted = currentUser.isPermitted( perm ); if ( logger.isDebugEnabled() ) { logger.debug( PATH_MSG, path, operation, perm, permitted ); @@ -462,7 +469,6 @@ public class SecuredResourceFilterFactory implements DynamicFeature { logger.debug("------------------------------------------------------------------------------"); } - } catch (Exception e){ throw mappableSecurityException( "unauthorized", "Subject does not have permission to access this resource" ); http://git-wip-us.apache.org/repos/asf/usergrid/blob/c7c7c8cd/stack/rest/src/test/java/org/apache/usergrid/rest/applications/SecurityIT.java ---------------------------------------------------------------------- diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/SecurityIT.java b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/SecurityIT.java index f64afe8..cbc8e38 100644 --- a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/SecurityIT.java +++ b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/SecurityIT.java @@ -17,7 +17,6 @@ package org.apache.usergrid.rest.applications; -import com.sun.jersey.api.client.UniformInterfaceException; import org.apache.usergrid.rest.test.resource.AbstractRestIT; import org.apache.usergrid.rest.test.resource.model.ApiResponse; import org.apache.usergrid.utils.UUIDUtils; http://git-wip-us.apache.org/repos/asf/usergrid/blob/c7c7c8cd/stack/rest/src/test/java/org/apache/usergrid/rest/applications/collection/users/PermissionsResourceIT.java ---------------------------------------------------------------------- diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/collection/users/PermissionsResourceIT.java b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/collection/users/PermissionsResourceIT.java index 9f6c4e4..b0b3791 100644 --- a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/collection/users/PermissionsResourceIT.java +++ b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/collection/users/PermissionsResourceIT.java @@ -17,21 +17,25 @@ package org.apache.usergrid.rest.applications.collection.users; -import java.util.List; -import java.util.UUID; - import net.jcip.annotations.NotThreadSafe; +import org.apache.commons.lang.RandomStringUtils; import org.apache.usergrid.rest.test.resource.AbstractRestIT; import org.apache.usergrid.rest.test.resource.model.*; -import org.apache.usergrid.services.exceptions.ServiceResourceNotFoundException; import org.apache.usergrid.utils.UUIDUtils; - import org.junit.Before; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.ws.rs.BadRequestException; import javax.ws.rs.ClientErrorException; +import javax.ws.rs.core.Form; import javax.ws.rs.core.Response; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; import static org.junit.Assert.*; @@ -41,6 +45,7 @@ import static org.junit.Assert.*; */ @NotThreadSafe public class PermissionsResourceIT extends AbstractRestIT { + private static final Logger logger = LoggerFactory.getLogger(PermissionsResourceIT.class); private static final String ROLE = "permtestrole"; @@ -562,18 +567,19 @@ public class PermissionsResourceIT extends AbstractRestIT { * @throws IOException */ private void addPermission( String rolename, String grant ) throws IOException { - //Create and post the permissions + + // Create and post the permissions Entity props = new Entity().chainPut("permission", grant); this.app().collection("roles").entity(rolename).collection("permissions").post(props); - //Checks that the permissions were added correctly + // Checks that the permissions were added correctly Collection node = this.app().collection("roles").entity(rolename).collection("permissions").get(); List<Object> data =(List) node.getResponse().getData(); - for(Object o : data){ - if(grant.equals(o.toString())){ + for ( Object o : data ) { + if ( grant.equals(o.toString()) ) { return; } } @@ -582,4 +588,105 @@ public class PermissionsResourceIT extends AbstractRestIT { } + @Test + public void testUsersMeAlwaysAvailable() { + + // delete default roles/permissions from app + + app().collection("roles").entity("default").delete(); + + // create an app user, get token (and switch context to that of user) + + Token token = null; + try { + String password = "s3cr3t"; + Entity newUser = app().collection( "users" ).post( + new User( "dave", "Dave Johnson", "d...@example.com", password ) ); + token = app().token().post( + new Token( "password", (String)newUser.get("username") , password )); + + } catch ( Exception e ) { + logger.error( "Error creating user and logging in: {}", e); + } + assertNotNull( token ); + + // user cannot post to a collection + + try { + Map<String, Object> catMap = new HashMap<String, Object>() {{ + put("name", "enzo"); + put("color", "orange"); + }}; + app().collection( "cats" ).post( true, token, ApiResponse.class, catMap, null, false ); + fail("Post should have failed"); + } catch ( Exception expected ) {} + + // but the /users/me end-point should work + + Entity me = app().collection( "users" ).entity( "me" ).get(); + assertNotNull( me ); + + try { + app().collection( "users" ).entity( "me" ).delete(); + fail("Delete /users/me must fail"); + } catch ( Exception expected ) {} + } + + + @Test + public void testAppUserNamedMeNotAllowed() { + + // cannot create app user named me + try { + app().collection( "users" ).post( new User( "me", "it's me", "m...@example.com", "me!" ) ); + fail("Must not be able to create app user named me"); + } catch ( BadRequestException expected ) {} + + // cannot use update to rename app user to me + Entity user = app().collection( "users" ).post( new User( "dave", "Sneaky Me", "m...@example.com", "me!" ) ); + try { + app().collection( "users" ).entity( user ).put( new Entity().chainPut( "username", "me" )); + fail("Must not be able to update app user to name me"); + + } catch ( BadRequestException expected ) {} + + } + + + @Test + public void testAdminUserNamedMeNotAllowed() { + + // cannot create admin user named me + try { + Form form = new Form(); + form.param( "username", "me" ); + form.param( "email", "m...@example.com"); + form.param( "name", "me Me ME!"); + form.param( "password", "me me 123" ); + management().users().post( ApiResponse.class, form ); + + fail("Must not be able to create admin user named me"); + + } catch ( BadRequestException expected ) {} + + // cannot use update to rename admin user to me + String randomString = RandomStringUtils.randomAlphanumeric( 10 ); + String username = "user_" + randomString; + String password = "me me 123"; + Form form = new Form(); + form.param( "username", username ); + form.param( "email", username + "@example.com"); + form.param( "name", "Despicable me"); + form.param( "password", password ); + management().users().post( ApiResponse.class, form ); + management().token().get( username, password ); + + try { + management().users().user( username ).put( true, new Entity().chainPut( "username", "me" ) ); + fail("Must not be able to create admin user named me"); + + } catch ( BadRequestException e ) {} + + } + } http://git-wip-us.apache.org/repos/asf/usergrid/blob/c7c7c8cd/stack/rest/src/test/java/org/apache/usergrid/rest/test/resource/endpoints/NamedResource.java ---------------------------------------------------------------------- diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/test/resource/endpoints/NamedResource.java b/stack/rest/src/test/java/org/apache/usergrid/rest/test/resource/endpoints/NamedResource.java index a2377d4..ad014d9 100644 --- a/stack/rest/src/test/java/org/apache/usergrid/rest/test/resource/endpoints/NamedResource.java +++ b/stack/rest/src/test/java/org/apache/usergrid/rest/test/resource/endpoints/NamedResource.java @@ -283,7 +283,7 @@ public class NamedResource implements UrlResource { if (useBasicAuthentication) { HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder() - .credentials("superuser", "superpassword").build(); + .credentials("superuser", "superpassword").build(); return resource.register(feature).request() .accept(MediaType.APPLICATION_JSON) .post(javax.ws.rs.client.Entity.json(entity), gt); @@ -343,6 +343,12 @@ public class NamedResource implements UrlResource { javax.ws.rs.client.Entity.entity(data, type), ApiResponse.class); } + public ApiResponse put(boolean useToken, org.apache.usergrid.rest.test.resource.model.Entity entity ) { + WebTarget resource = getTarget(useToken); + return resource.request().put( + javax.ws.rs.client.Entity.entity(entity, MediaType.APPLICATION_JSON_TYPE), ApiResponse.class); + } + public ApiResponse put(byte[] data, MediaType type) { return put(true, data, type); } http://git-wip-us.apache.org/repos/asf/usergrid/blob/c7c7c8cd/stack/services/src/main/java/org/apache/usergrid/security/shiro/principals/ApplicationUserPrincipal.java ---------------------------------------------------------------------- diff --git a/stack/services/src/main/java/org/apache/usergrid/security/shiro/principals/ApplicationUserPrincipal.java b/stack/services/src/main/java/org/apache/usergrid/security/shiro/principals/ApplicationUserPrincipal.java index 65503b4..41c84ae 100644 --- a/stack/services/src/main/java/org/apache/usergrid/security/shiro/principals/ApplicationUserPrincipal.java +++ b/stack/services/src/main/java/org/apache/usergrid/security/shiro/principals/ApplicationUserPrincipal.java @@ -33,12 +33,14 @@ import org.apache.usergrid.persistence.entities.User; import org.apache.usergrid.security.shiro.Realm; import org.apache.usergrid.security.shiro.UsergridAuthorizationInfo; import org.apache.usergrid.security.shiro.credentials.AccessTokenCredentials; +import org.apache.usergrid.security.shiro.utils.SubjectUtils; import org.apache.usergrid.security.tokens.TokenInfo; import org.apache.usergrid.security.tokens.TokenService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.usergrid.security.shiro.utils.SubjectUtils.getPermissionFromPath; +import static org.apache.usergrid.security.shiro.utils.SubjectUtils.getSubject; public class ApplicationUserPrincipal extends UserPrincipal { @@ -94,13 +96,10 @@ public class ApplicationUserPrincipal extends UserPrincipal { grant( info, getPermissionFromPath( applicationId, "access" ) ); - /* - * grant(info, principal, getPermissionFromPath(applicationId, - * "get,put,post,delete", "/users/${user}", - * "/users/${user}/feed", "/users/${user}/activities", - * "/users/${user}/groups", "/users/${user}/following/*", - * "/users/${user}/following/user/*")); - */ + if ( SubjectUtils.getUser() != null ) { + // ensure that the /org/app/users/me end-point always works for logged in user + grant( info, "applications:get:" + applicationId + ":/users/" + SubjectUtils.getUser().getUuid() ); + } EntityManager em = emf.getEntityManager( applicationId ); try {