/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.usergrid.rest.management; import com.fasterxml.jackson.databind.JsonNode; import net.jcip.annotations.NotThreadSafe; import org.apache.commons.lang.RandomStringUtils; import org.apache.usergrid.persistence.index.utils.UUIDUtils; import org.apache.usergrid.rest.management.organizations.OrganizationsResource; import org.apache.usergrid.rest.test.resource.AbstractRestIT; import org.apache.usergrid.rest.test.resource.model.*; import org.apache.usergrid.rest.test.resource.model.Collection; import org.apache.usergrid.security.tokens.cassandra.TokenServiceImpl; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.ClientErrorException; import javax.ws.rs.core.Form; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.IOException; import java.util.*; import static org.apache.usergrid.security.tokens.cassandra.TokenServiceImpl.USERGRID_EXTERNAL_SSO_PROVIDER; import static org.apache.usergrid.security.tokens.cassandra.TokenServiceImpl.USERGRID_EXTERNAL_SSO_PROVIDER_URL; import static org.apache.usergrid.security.tokens.cassandra.TokenServiceImpl.USERGRID_EXTERNAL_SSO_ENABLED; import static org.apache.usergrid.utils.MapUtils.hashMap; import static org.junit.Assert.*; /** * @author tnine */ @NotThreadSafe // due to use of /testproperties end-point public class ManagementResourceIT extends AbstractRestIT { private static final Logger logger = LoggerFactory.getLogger(ManagementResourceIT.class); private org.apache.usergrid.rest.test.resource.endpoints.mgmt.ManagementResource management; public ManagementResourceIT() throws Exception { } @Before public void setup() { management= clientSetup.getRestClient().management(); Token token = management.token() .get( new QueryParameters() .addParam( "grant_type", "password" ) .addParam( "username", clientSetup.getEmail() ) .addParam( "password", clientSetup.getPassword() ) ); management.token().setToken(token); } /** * Test if we can reset our password as an admin */ @Test public void setSelfAdminPasswordAsAdmin() { UUID uuid = UUIDUtils.newTimeUUID(); management.token().setToken(clientSetup.getSuperuserToken()); management.orgs() .org( clientSetup.getOrganizationName() ) .users() .post( ApiResponse.class, new User( "test" + uuid, "test" + uuid, "test" + uuid + "@email.com", "test" ) ); Map<String, Object> data = new HashMap<>(); data.put( "newpassword", "foofoo" ); data.put( "oldpassword", "test" ); management.users() .user( "test" + uuid ) .password() .post( Entity.class, data ); Token token = management.token().post(Token.class, new Token( "test"+uuid, "foo" ) ); management.token().setToken( token ); data.clear(); data.put( "oldpassword", "foofoo" ); data.put( "newpassword", "test" ); management.users().user("test"+uuid).password().post(Entity.class, data); } /** * Test that admins can't view organizations they're not authorized to view. */ @Test public void crossOrgsNotViewable() throws Exception { String differentiator = UUIDUtils.newTimeUUID().toString(); String username = "test" + differentiator; String name = "someguy2" + differentiator; String email = "someguy" + differentiator + "@usergrid.com"; String password = "password"; String orgName = "someneworg" + differentiator; Entity payload = new Entity().chainPut("company", "Apigee" ); Organization organization = new Organization(orgName,username,email,name,password,payload); Organization node = management().orgs().post( organization ); management.token().get(clientSetup.getUsername(), clientSetup.getPassword()); // check that the test admin cannot access the new org info // management/organizations/{orgName} Response.Status status = null; try { this.management().orgs().org( orgName ).get(String.class); } catch ( ClientErrorException uie ) { status = Response.Status.fromStatusCode( uie.getResponse().getStatus() ); } assertNotNull( status ); assertEquals( Response.Status.UNAUTHORIZED, status ); // management/organizations/{orgName}/users status = null; try { this.management().orgs().org( orgName ).users().get( String.class ); } catch ( ClientErrorException uie ) { status = Response.Status.fromStatusCode( uie.getResponse().getStatus() ); } assertNotNull( status ); assertEquals( Response.Status.UNAUTHORIZED, status ); // management/organizations/{orgName}/applications status = null; try { this.management().orgs().org( orgName ).applications().get( String.class ); } catch ( ClientErrorException uie ) { status = Response.Status.fromStatusCode( uie.getResponse().getStatus() ); } assertNotNull( status ); assertEquals( Response.Status.UNAUTHORIZED, status ); // this admin should have access to test org status = null; try { this.management().orgs().org( this.clientSetup.getOrganizationName() ).get( String.class ); } catch ( ClientErrorException uie ) { status = Response.Status.fromStatusCode( uie.getResponse().getStatus() ); } assertNull( status ); // this admin should have access to test org - users status = null; try { this.management().orgs().org( this.clientSetup.getOrganizationName() ).users().get( String.class ); } catch ( ClientErrorException uie ) { status = Response.Status.fromStatusCode( uie.getResponse().getStatus() ); } assertNull(status); // this admin should have access to test org - apps status = null; try { this.management().orgs().org( this.clientSetup.getOrganizationName() ).applications().get( String.class ); } catch ( ClientErrorException uie ) { status = Response.Status.fromStatusCode( uie.getResponse().getStatus() ); } assertNull(status); // test getting the organization by org status = null; try { this.management().orgs().org( this.clientSetup.getOrganizationName() ).get( String.class ); } catch ( ClientErrorException uie ) { status = Response.Status.fromStatusCode( uie.getResponse().getStatus() ); } assertNull(status); } /** * Test that we can support over 10 items in feed. */ @Test public void mgmtFollowsUserFeed() throws Exception { List<String> users1 = new ArrayList<String>(); int i; //try with 10 users for ( i = 0; i < 10; i++ ) { users1.add( "follower" + Integer.toString( i ) ); } waitForQueueDrainAndRefreshIndex( ); checkFeed( "leader1", users1 ); //try with 11 List<String> users2 = new ArrayList<String>(); for ( i = 20; i < 31; i++ ) { users2.add( "follower" + Integer.toString( i ) ); } checkFeed( "leader2", users2 ); } private void checkFeed( String leader, List<String> followers ) throws IOException { List<Entity> userFeed; //create user createUser( leader ); waitForQueueDrainAndRefreshIndex( ); String preFollowContent = leader + ": pre-something to look for " + UUID.randomUUID().toString(); addActivity( leader, leader + " " + leader + "son", preFollowContent ); waitForQueueDrainAndRefreshIndex( ); String lastUser = followers.get( followers.size() - 1 ); int i = 0; for ( String user : followers ) { createUser( user ); waitForQueueDrainAndRefreshIndex( ); follow( user, leader ); waitForQueueDrainAndRefreshIndex( ); } userFeed = getUserFeed( lastUser ); assertTrue( userFeed.size() == 1 ); //retrieve feed userFeed = getUserFeed( lastUser ); assertTrue( userFeed.size() == 1 ); String postFollowContent = leader + ": something to look for " + UUID.randomUUID().toString(); addActivity( leader, leader + " " + leader + "son", postFollowContent ); waitForQueueDrainAndRefreshIndex( ); //check feed userFeed = getUserFeed( lastUser ); assertNotNull( userFeed ); assertTrue( userFeed.size() > 1 ); String serialized = ((Entity)userFeed.get(0)) .get( "content" ) .toString()+ ((Entity)userFeed.get(1)) .get( "content" ).toString(); assertTrue( serialized.indexOf( postFollowContent ) >= 0 ); assertTrue( serialized.indexOf( preFollowContent ) >= 0 ); } private void createUser( String username ) { Map<String, Object> payload = new LinkedHashMap<String, Object>(); payload.put( "username", username ); this.app().collection("users").post(String.class, payload); } private List<Entity> getUserFeed( String username ) throws IOException { Collection collection = this.app().collection("users").entity(username).collection("feed").get(); return collection.getResponse().getEntities(); } private void follow( String user, String followUser ) { //post follow Entity entity = this.app() .collection( "users" ) .entity(user) .collection("following") .collection("users") .entity(followUser) .post(); } private void addActivity( String user, String name, String content ) { Map<String, Object> activityPayload = new HashMap<String, Object>(); activityPayload.put( "content", content ); activityPayload.put( "verb", "post" ); Map<String, String> actorMap = new HashMap<String, String>(); actorMap.put( "displayName", name ); actorMap.put( "username", user ); activityPayload.put("actor", actorMap); Entity entity = this.app() .collection( "users" ) .entity(user) .collection("activities") .post( new Entity( activityPayload ) ); } @Test public void mgmtCreateAndGetApplication() throws Exception { // POST /applications ApiResponse apiResponse = management() .orgs() .org( clientSetup.getOrganizationName() ) .app() .post( new Application( "mgmt-org-app" ) ); waitForQueueDrainAndRefreshIndex(); Entity appdata = apiResponse.getEntities().get(0); assertEquals((clientSetup.getOrganizationName() + "/mgmt-org-app") .toLowerCase(), appdata.get("name") .toString() .toLowerCase()); assertNotNull(appdata.get("metadata")); Map metadata =(Map) appdata.get( "metadata" ); assertNotNull(metadata.get("collections")); Map collections = ((Map)metadata.get("collections")); assertNotNull(collections.get("roles")); Map roles =(Map) collections.get("roles"); assertNotNull(roles.get("title")); assertEquals("Roles", roles.get("title").toString()); assertEquals(4, roles.size()); waitForQueueDrainAndRefreshIndex( ); // GET /applications/mgmt-org-app Entity app = management().orgs().org( clientSetup.getOrganizationName() ).app().addToPath("mgmt-org-app").get(); assertEquals(this.clientSetup.getOrganizationName().toLowerCase(), app.get("organizationName").toString()); assertEquals( "mgmt-org-app", app.get( "applicationName" ).toString() ); assertEquals( clientSetup.getOrganizationName().toLowerCase() + "/mgmt-org-app", app.get( "name" ).toString() ); metadata =(Map) appdata.get( "metadata" ); collections = ((Map)metadata.get("collections")); roles =(Map) collections.get("roles"); assertEquals( "Roles", roles.get("title").toString() ); assertEquals(4, roles.size()); } @Test public void checkSizes() throws Exception { final String appname = clientSetup.getAppName(); this.app().collection("testCollection").post(new Entity().chainPut("name","test")); waitForQueueDrainAndRefreshIndex(); Entity size = management().orgs().org( clientSetup.getOrganizationName() ).app().addToPath(appname).addToPath("_size").get(); Entity rolesSize = management().orgs().org(clientSetup.getOrganizationName()).app().addToPath(appname).addToPath("roles/_size").get(); Entity collectionsSize = management().orgs().org(clientSetup.getOrganizationName()).app().addToPath(appname).addToPath("collections/_size").get(); assertTrue(size != null); assertTrue(rolesSize != null); int sum = (int)((LinkedHashMap)((LinkedHashMap)size.metadata().get("aggregation")).get("size")).get("application"); int sumRoles = (int)((LinkedHashMap)((LinkedHashMap)rolesSize.metadata().get("aggregation")).get("size")).get("roles"); int sumRoles2 = (int)((LinkedHashMap)((LinkedHashMap)collectionsSize.metadata().get("aggregation")).get("size")).get("roles"); assertTrue(size != null); assertTrue(rolesSize != null); assertNotEquals(sum, sumRoles); assertTrue(sum > sumRoles); assertTrue(sumRoles == sumRoles2); } @Test public void tokenTtl() throws Exception { long ttl = 2000; Token token = management.token().get( new QueryParameters() .addParam( "grant_type", "password" ) .addParam( "username", clientSetup.getEmail() ) .addParam( "password", clientSetup.getPassword() ) .addParam( "ttl", String.valueOf( ttl ) ) ); long startTime = System.currentTimeMillis(); assertNotNull( token ); Entity userdata = management.users().entity(clientSetup.getEmail()).get(token); assertNotNull(userdata.get("email").toString()); // wait for the token to expire Thread.sleep( (System.currentTimeMillis() - startTime) + ttl ); Response.Status responseStatus = null; try { userdata = management.users().user(clientSetup.getEmail()).get(); } catch ( ClientErrorException uie ) { responseStatus = Response.Status.fromStatusCode( uie.getResponse().getStatus() ); } assertEquals( Response.Status.UNAUTHORIZED, responseStatus ); } @Test public void token() throws Exception { Token myToken = management.token() .get( new QueryParameters() .addParam( "grant_type", "password" ) .addParam( "username", clientSetup.getEmail() ) .addParam( "password", clientSetup.getPassword() ) ); String token = myToken.getAccessToken(); assertNotNull( token ); // set an organization property Organization payload = new Organization(); Map<String, Object> properties = new HashMap<String, Object>(); properties.put( "securityLevel", 5 ); payload.put( OrganizationsResource.ORGANIZATION_PROPERTIES, properties ); management.orgs().org( clientSetup.getOrganizationName() ).put( payload ); // ensure the organization property is included String obj = management.token().get(String.class,new QueryParameters().addParam("access_token", token)); assertTrue(obj.indexOf("securityLevel")>0); } @Test public void meToken() throws Exception { QueryParameters queryParameters = new QueryParameters() .addParam( "grant_type", "password" ) .addParam( "username", clientSetup.getUsername() ) .addParam( "password", clientSetup.getPassword() ); Token myToken = management.me().get( Token.class, queryParameters ); String token = myToken.getAccessToken(); assertNotNull( token ); Entity entity = management.me().get( Entity.class ); assertNotNull( entity.get( "passwordChanged" ) ); assertNotNull( entity.get( "access_token" ) ); assertNotNull( entity.get( "expires_in" ) ); Map<String,Object> userNode =(Map<String,Object>) entity.get( "user" ); assertNotNull( userNode ); assertNotNull( userNode.get( "uuid" ) ); assertNotNull( userNode.get( "username" ) ); assertNotNull( userNode.get( "email" ) ); assertNotNull( userNode.get( "name" ) ); assertNotNull( userNode.get( "properties" ) ); Map<String,Object> orgsNode = (Map<String,Object>) userNode.get( "organizations" ); assertNotNull( orgsNode ); Map<String,Object> orgNode =(Map<String,Object>) orgsNode.entrySet().iterator().next().getValue(); assertNotNull( orgNode ); assertNotNull( orgNode.get( "name" ) ); assertNotNull( orgNode.get( "properties" ) ); } @Test public void meTokenPost() throws Exception { Map<String, String> payload = hashMap( "grant_type", "password" ) .map( "username", clientSetup.getUsername() ) .map( "password", clientSetup.getPassword() ); JsonNode node = management.me().post( JsonNode.class, payload ); logger.info("node:", node); String token = node.get( "access_token" ).textValue(); assertNotNull( token ); node = management.me().get( JsonNode.class ); } @Test public void meTokenPostForm() { Form form = new Form(); form.param( "grant_type", "password" ); form.param( "username", clientSetup.getUsername() ); form.param( "password", clientSetup.getPassword() ); JsonNode node = management.me().post( JsonNode.class, form ); logger.info( "node:", node); String token = node.get( "access_token" ).textValue(); assertNotNull( token ); node = target().path( "/management/me" ) .queryParam( "access_token", token ).request() .accept( MediaType.APPLICATION_JSON ) .get( JsonNode.class ); logger.info("node:", node ); } @Test public void ttlNan() throws Exception { Map<String, String> payload = hashMap( "grant_type", "password" ) .map( "username", clientSetup.getUsername() ) .map( "password", clientSetup.getPassword() ) .map( "ttl", "derp" ); Response.Status responseStatus = null; try { management.token().post( JsonNode.class, payload ); } catch ( ClientErrorException uie ) { responseStatus = Response.Status.fromStatusCode( uie.getResponse().getStatus()); } assertEquals( Response.Status.BAD_REQUEST, responseStatus ); } @Test public void ttlOverMax() throws Exception { Map<String, String> payload = hashMap( "grant_type", "password" ) .map( "username", clientSetup.getUsername() ) .map( "password", clientSetup.getPassword() ) .map( "ttl", Long.MAX_VALUE + "" ); Response.Status responseStatus = null; try { management.token().post( JsonNode.class, payload ); } catch ( ClientErrorException uie ) { responseStatus = Response.Status.fromStatusCode( uie.getResponse().getStatus() ); } assertEquals( Response.Status.BAD_REQUEST, responseStatus ); } @Test public void revokeToken() throws Exception { Token token1 = management.token().get(clientSetup.getUsername(),clientSetup.getPassword()); Entity response = management.users().user(clientSetup.getUsername()).get(); assertNotNull(response.get("email").toString()); response = management.users().user(clientSetup.getUsername()).get(); assertNotNull(response.get("email").toString()); // now revoke the tokens response = management.users().user( clientSetup.getUsername()).revokeTokens().post(true,Entity.class,null, null,false); // the tokens shouldn't work Response.Status status = null; try { response = management.users().user(clientSetup.getUsername()).get(); } catch ( ClientErrorException uie ) { status = Response.Status.fromStatusCode( uie.getResponse().getStatus() ); } assertEquals( Response.Status.UNAUTHORIZED, status ); Token token3 = management.token().get(clientSetup.getUsername(), clientSetup.getPassword()); response = management.users().user(clientSetup.getUsername()).get(); assertNotNull(response.get("email").toString()); // now revoke the token3 QueryParameters queryParameters = new QueryParameters(); queryParameters.addParam( "token", token3.getAccessToken() ); management.users().user( clientSetup.getUsername()).revokeToken().post( false, Entity.class,null,queryParameters ); // the token3 shouldn't work status = null; try { management.users().user(clientSetup.getUsername()).get(); } catch ( ClientErrorException uie ) { status = Response.Status.fromStatusCode( uie.getResponse().getStatus() ); } assertEquals( Response.Status.UNAUTHORIZED, status ); } @Test public void testValidateExternalToken() throws Exception { // create a new admin user, get access token String rand = RandomStringUtils.randomAlphanumeric(10); final String username = "user_" + rand; management().orgs().post( new Organization( username, username, username+"@example.com", username, "password", null ) ); Map<String, Object> loginInfo = new HashMap<String, Object>() {{ put("username", username ); put("password", "password" ); put("grant_type", "password"); }}; JsonNode accessInfoNode = management.token() .post( JsonNode.class, loginInfo ); String accessToken = accessInfoNode.get( "access_token" ).asText(); // set the Usergrid Central SSO URL because Tomcat port is dynamically assigned String suToken = clientSetup.getSuperuserToken().getAccessToken(); Map<String, String> props = new HashMap<String, String>(); props.put(USERGRID_EXTERNAL_SSO_PROVIDER_URL, getBaseURI().toURL().toExternalForm() ); pathResource( "testproperties" ).post( props ); // TODO: how do we unit test SSO now that we have no external token end-point? JsonNode node = pathResource("/management/me").get( JsonNode.class, new QueryParameters() .addParam( "access_token", accessToken) ); logger.info( "node: {}", node ); String token = node.get( "access_token" ).asText(); assertNotNull( token ); // TODO: how do we test the create new user and organization case? // unset the Usergrid Central SSO URL so it does not interfere with other tests props.put(USERGRID_EXTERNAL_SSO_PROVIDER_URL, "" ); pathResource( "testproperties" ).post( props ); } @Test public void testSuperuserOnlyWhenValidateExternalTokensEnabledForUsergridProvider() throws Exception { // create an org and an admin user String rand = RandomStringUtils.randomAlphanumeric( 10 ); final String username = "user_" + rand; management().orgs().post( new Organization( username, username, username+"@example.com", username, "password", null ) ); // turn on validate external tokens by setting the usergrid.central.url String suToken = clientSetup.getSuperuserToken().getAccessToken(); Map<String, String> props = new HashMap<String, String>(); props.put(USERGRID_EXTERNAL_SSO_PROVIDER, "usergrid"); props.put(USERGRID_EXTERNAL_SSO_ENABLED, "true"); props.put(USERGRID_EXTERNAL_SSO_PROVIDER_URL, getBaseURI().toURL().toExternalForm() ); pathResource( "testproperties" ).post( props ); try { // calls to login as an Admin User must now fail try { Map<String, Object> loginInfo = new HashMap<String, Object>() {{ put( "username", username ); put( "password", "password" ); put( "grant_type", "password" ); }}; ApiResponse postResponse = pathResource( "management/token" ).post( false, ApiResponse.class, loginInfo ); fail( "External SSO integration is enabled, admin users must login via provider using configured property: "+ TokenServiceImpl.USERGRID_EXTERNAL_SSO_PROVIDER ); } catch (ClientErrorException actual) { assertEquals( 400, actual.getResponse().getStatus() ); String errorMsg = actual.getResponse().readEntity( JsonNode.class ) .get( "error_description" ).toString(); logger.error( "ERROR: " + errorMsg ); assertTrue( errorMsg.contains( "admin users must login via" ) ); } catch (Exception e) { fail( "We expected a ClientErrorException" ); } // login as superuser must succeed Map<String, Object> loginInfo = new HashMap<String, Object>() {{ put( "username", "superuser" ); put( "password", "superpassword" ); put( "grant_type", "password" ); }}; ApiResponse postResponse2 = pathResource( "management/token" ).post( loginInfo ); String accessToken = postResponse2.getAccessToken(); assertNotNull( accessToken ); //Superuser : GET -> get tokenInfo with access_token ApiResponse getResponse3 = pathResource("management/me").get(ApiResponse.class,new QueryParameters() .addParam("grant_type", "password").addParam("password", "superpassword") .addParam("username", "superuser"),false); assertNotNull(getResponse3.getAccessToken()); //Superuser : POST -> Add org using super user credentials. Map<String, Object> orgAdminUserInfo = new HashMap<String, Object>() {{ put( "username", username+"test" ); put("password","RandomPassword"); put("email",username+"@gmail.com"); put( "organization", username+"RandomOrgName" ); }}; ApiResponse postResponse4 = pathResource("management/orgs") .post(false,orgAdminUserInfo,new QueryParameters().addParam("access_token",getResponse3.getAccessToken())); assertNotNull(postResponse4.getData()); } finally { // turn off validate external tokens by un-setting the usergrid.central.url props.put(USERGRID_EXTERNAL_SSO_PROVIDER_URL, "" ); props.put(USERGRID_EXTERNAL_SSO_ENABLED, ""); pathResource( "testproperties" ).post( props ); } } }