/* * 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.applications.collection.users; import java.io.IOException; import org.apache.usergrid.rest.test.resource.AbstractRestIT; import org.apache.usergrid.rest.test.resource.endpoints.CollectionEndpoint; import org.apache.usergrid.rest.test.resource.model.*; import org.junit.Before; import org.junit.Test; import javax.ws.rs.ClientErrorException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * Take in tests that handle owner permissions relating to a specific user. */ public class OwnershipResourceIT extends AbstractRestIT { private CollectionEndpoint usersResource; private User user1; private User user2; /** * Setup two user objects for use in the following tests. */ @Before public void setup(){ this.usersResource = this.app().collection("users"); String email = "testuser1@usergrid.org"; String email2 = "testuser2@usergrid.org"; user1 = new User("testuser1","testuser1", email, "password" ); user2 = new User("testuser2","testuser2", email2, "password" ); user1 = new User(this.usersResource.post(user1)); user2 = new User(this.usersResource.post(user2)); waitForQueueDrainAndRefreshIndex(); } /** * Verifies that me and user1 are the same and that we can revoke the token from user1 and have the me call fail. * @throws Exception */ @Test public void meVerify() throws Exception { //Clear the applications previous token and start anonymous this.app().token().clearToken(); //Set the token for getting the me entity. Token token = this.app().token().post(new Token(user1.getUsername(),"password")); //Get the entity known as "me" out of the users collection and asserts it isn't null and is equal to user1. Entity userNode = usersResource.entity("me").get(); assertNotNull( userNode ); assertEquals( user1.getName(), userNode.get( "username" ) ); //Gets the uuid of the me entity. String uuid = userNode.getUuid().toString(); assertNotNull( uuid ); //Revoke the user1 token usersResource.entity(user1).connection("revoketokens").post(new Entity().chainPut("token", token.getAccessToken())); waitForQueueDrainAndRefreshIndex(); //See if we can still access the me entity after revoking its token try { usersResource.entity("me").get(); fail("This should not work after we've revoked the usertoken"); } catch ( Exception ex ) { ex.printStackTrace(); assertTrue( ex.getMessage().contains( "401" ) ); } } /** * Checks that that can only see our own devices when looking at our own path. Then checks that we can see * both devices from a root path. * @throws IOException */ @Test public void contextualPathOwnership() throws IOException { //Clear the applications previous token and start anonymous this.app().token().clearToken(); //Setting the token to be in a user1 context. this.app().token().post(new Token(user1.getUsername(),"password")); // create device 1 on user1 devices usersResource.entity("me").collection("devices") .post(new Entity( ).chainPut("name", "device1").chainPut("number", "5551112222")); waitForQueueDrainAndRefreshIndex(); //Clear the current user token this.app().token().clearToken(); // create device 2 on user 2 and switch the context to use user2 Token token = this.app().token().post(new Token(user2.getUsername(),"password")); usersResource.entity("me").collection("devices") .post(new Entity( ).chainPut("name", "device2").chainPut("number", "5552223333")); waitForQueueDrainAndRefreshIndex(); //Check that we can get back device1 on user1 token = this.app().token().post(new Token(user1.getUsername(),"password")); CollectionEndpoint devices = usersResource.entity( user1 ).collection("devices"); Entity data = devices.entity("device1").get(); assertNotNull( data ); assertEquals("device1", data.get("name").toString()); // check we can't see device2 on user1 int status = 0; try { data = devices.entity("device2").get(); }catch (ClientErrorException e){ status = e.getResponse().getStatus(); } assertEquals( status,404 ); // do a collection load, make sure we're not loading device 2 Collection devicesData = devices.get(); assertEquals("device1", devicesData.next().get("name").toString()); assertTrue(!devicesData.hasNext()); // log in as user 2 and check that we can see device 2 token = this.app().token().post(new Token(user2.getUsername(),"password")); devices = usersResource.entity("me").collection("devices"); data = devices.entity("device2").get(); assertNotNull( data ); assertEquals( "device2", data.get("name").toString() ); // check we can't see device1 on user2 status = 0; try{ data = devices.entity("device1").get(); }catch (ClientErrorException e){ status = e.getResponse().getStatus(); } assertEquals( status,404 ); // do a collection load, make sure we're not loading device 1 devicesData = devices.get(); assertEquals("device2", devicesData.next().get("name").toString()); assertTrue(!devicesData.hasNext()); // we should see both devices when loaded from the root application devices = this.app().collection("devices"); // test we can see both devices for user 1 under root application token = this.app().token().post(new Token(user1.getUsername(),"password")); data = devices.entity("device1").get(); assertNotNull( data ); assertEquals( "device1", data.get("name").toString() ); data = devices.entity("device2").get(); assertNotNull( data ); assertEquals( "device2", data.get("name").toString() ); // test we can see both devices for user 2 under root application token = this.app().token().post(new Token(user2.getUsername(),"password")); data = devices.entity( "device1" ).get(); assertNotNull( data ); assertEquals( "device1",data.get( "name" ).toString() ); data = devices.entity( "device2" ).get(); assertNotNull( data ); assertEquals( "device2",data.get( "name" ).toString() ); } /** * Tests that we can have our own personal connections without being seen by other users, but are still visible * from a root context. * @throws IOException */ @Test public void contextualConnectionOwnership() throws IOException { // anonymous user this.app().token().clearToken(); //Setting the token to be in a user1 context. this.app().token().post(new Token(user1.getUsername(),"password")); // create a 4peaks restaurant Entity data = this.app().collection("restaurants").post(new Entity().chainPut("name", "4peaks")); waitForQueueDrainAndRefreshIndex(); //Create a restaurant and link it to user1/me Entity fourPeaksData = usersResource.entity("me") .connection("likes").collection( "restaurants" ).entity( "4peaks" ).post(); waitForQueueDrainAndRefreshIndex(); // anonymous user this.app().token().clearToken(); // create a restaurant and link it to user 2 this.app().token().post(new Token(user2.getUsername(),"password")); data = this.app().collection("restaurants") .post(new Entity().chainPut("name", "arrogantbutcher")); waitForQueueDrainAndRefreshIndex(); data = usersResource.entity("me").connection( "likes" ).collection( "restaurants" ) .entity( "arrogantbutcher" ).post(); waitForQueueDrainAndRefreshIndex(); String arrogantButcherId = data.getUuid().toString(); //Setting the token to be in a user1 context. this.app().token().post(new Token(user1.getUsername(),"password")); //Gets the connection between user1 and the their restaurants. In this case gets 4peaks CollectionEndpoint likeRestaurants = usersResource.entity( "me" ).connection( "likes" ) .collection( "restaurants" ); //Check that we can get the 4peaks restaurant by using its uuid String peaksId = fourPeaksData.getUuid().toString(); data = likeRestaurants.entity(peaksId).get(); assertNotNull( data ); assertEquals("4peaks", data.get("name").toString()); //Check that we can get the restaurant by name data = likeRestaurants.entity( "4peaks" ).get(); assertNotNull( data ); assertEquals( "4peaks", data.get("name").toString() ); // check we can't see arrogantbutcher by name or id from user1 int status = 200; try { likeRestaurants.entity("arrogantbutcher").get(); }catch (ClientErrorException e){ status = e.getResponse().getStatus(); } assertEquals(status, 404); status = 200; try { likeRestaurants.entity( arrogantButcherId ).get(); }catch (ClientErrorException e){ status = e.getResponse().getStatus(); } assertEquals(status, 404); // do a collection load, make sure we're not getting entities we shouldn't see Collection collectionData = likeRestaurants.get(); assertEquals("4peaks", collectionData.next().get("name").toString()); assertTrue( !collectionData.hasNext() ); // log in as user 2 and check that we can see the arrogantbutcher this.app().token().post(new Token(user2.getUsername(),"password")); likeRestaurants = usersResource.entity("me").connection( "likes" ) .collection( "restaurants" ); data = likeRestaurants.entity( arrogantButcherId ).get(); assertNotNull( data ); assertEquals( "arrogantbutcher", data.get("name").toString() ); data = likeRestaurants.entity( "arrogantbutcher" ).get(); assertNotNull( data ); assertEquals( "arrogantbutcher", data.get("name").toString() ); // check we can't see 4peaks as user2 status = 200; try { data = likeRestaurants.entity( "4peaks" ).get(); }catch (ClientErrorException e){ status = e.getResponse().getStatus(); } assertEquals( status,404 ); status = 200; try { likeRestaurants.entity( peaksId ).get(); }catch (ClientErrorException e){ status = e.getResponse().getStatus(); } assertEquals( status,404 ); // do a collection load, make sure we're not loading device 1 collectionData = likeRestaurants.get(); assertEquals("arrogantbutcher", collectionData.next().get("name").toString()); assertTrue( !collectionData.hasNext() ); // we should see both devices when loaded from the root application //Check we can see both restaurants as user1 from the root application this.app().token().post(new Token(user1.getUsername(),"password")); CollectionEndpoint restaurants = this.app().collection( "restaurants" ); data = restaurants.entity( "4peaks" ).get(); assertNotNull( data ); assertEquals( "4peaks", data.get("name").toString() ); data = restaurants.entity( "arrogantbutcher" ).get(); assertNotNull( data ); assertEquals( "arrogantbutcher", data.get("name").toString() ); //Check we can see both restaurants as user2 from the root application this.app().token().post(new Token(user2.getUsername(),"password")); restaurants = this.app().collection("restaurants"); data = restaurants.entity( "4peaks" ).get(); assertNotNull( data ); assertEquals( "4peaks",data.get("name").toString() ); data = restaurants.entity( "arrogantbutcher" ).get(); assertNotNull( data ); assertEquals( "arrogantbutcher",data.get("name").toString() ); } /** * Checks that a once guests permissions are opened up that a user can view the connections/entities * and get/post/delete things on that connection. * @throws IOException */ @Test public void contextualConnectionOwnershipGuestAccess() throws IOException { //set up full GET,PUT,POST,DELETE access for guests this.app().collection("roles").entity( "guest" ).collection( "permissions" ) .post(new Entity().chainPut("permission", "get,put,post,delete:/**")); //Sets up the cities collection with the city tempe Entity city = this.app().collection("cities").post(new Entity().chainPut("name", "tempe")); waitForQueueDrainAndRefreshIndex(); // create a 4peaks restaurant that is connected by a like to tempe. Entity data = this.app().collection("cities").entity( "tempe" ).connection( "likes" ) .collection( "restaurants" ).post(new Entity().chainPut("name", "4peaks")); String peaksId = data.get("uuid").toString(); // create the arrogantbutcher restaurant that is connected by a like to tempe. data = this.app().collection("cities").entity( "tempe" ).connection( "likes" ) .collection( "restaurants" ).post(new Entity().chainPut("name", "arrogantbutcher")); String arrogantButcherId = data.get("uuid").toString(); //Set the user to user1 and get the collection cities this.app().token().post(new Token(user1.getUsername(),"password")); CollectionEndpoint likeRestaurants = this.app().collection("cities").entity( "tempe" ).connection( "likes" ); waitForQueueDrainAndRefreshIndex(); // check we can get the resturant entities back via uuid without a collection name data = likeRestaurants.entity( peaksId ).get(); assertNotNull( data ); assertEquals( "4peaks", data.get("name").toString() ); data = likeRestaurants.entity( arrogantButcherId ).get(); assertEquals( "arrogantbutcher", data.get("name").toString() ); // check we can get the restaurant via uuid with a collection name data = likeRestaurants.collection( "restaurants" ).entity( peaksId ).get(); assertNotNull( data ); assertEquals( "4peaks", data.get("name").toString() ); data = likeRestaurants.collection( "restaurants" ).entity( arrogantButcherId ).get(); assertEquals( "arrogantbutcher", data.get("name").toString() ); // Delete the restaurants, either token should work for deletion ApiResponse deleteResponse = likeRestaurants.collection( "restaurants" ).entity( peaksId ).delete(); assertNotNull( deleteResponse ); assertEquals( "4peaks", deleteResponse.getEntities().get(0).get("name").toString() ); deleteResponse = likeRestaurants.collection( "restaurants" ).entity( arrogantButcherId ).delete(); assertNotNull( deleteResponse ); assertEquals( "arrogantbutcher", deleteResponse.getEntities().get(0).get("name").toString() ); } }