diff options
author | Jon Marius Venstad <jvenstad@yahoo-inc.com> | 2019-04-09 16:32:09 +0200 |
---|---|---|
committer | Jon Marius Venstad <jvenstad@yahoo-inc.com> | 2019-04-09 16:32:09 +0200 |
commit | 5dd6cc0f37e17c15082e5b80566977b95898df59 (patch) | |
tree | 555698e80fcecfc68f7276226f3b61cceafb349f /controller-server | |
parent | 8c0dd10b628295b7add03a01cd4efc8d6620dcb6 (diff) |
Unit test user management and application APIs with alternative components
Diffstat (limited to 'controller-server')
5 files changed, 246 insertions, 9 deletions
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java index a98b2b60bf8..caf0ec82aae 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java @@ -1,24 +1,152 @@ package com.yahoo.vespa.hosted.controller.restapi.user; -import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; -import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; -import org.junit.Ignore; +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.hosted.controller.api.role.Role; +import com.yahoo.vespa.hosted.controller.api.role.Roles; +import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; +import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest; import org.junit.Test; +import java.io.File; +import java.util.Set; + +import static com.yahoo.application.container.handler.Request.Method.DELETE; +import static com.yahoo.application.container.handler.Request.Method.POST; +import static com.yahoo.application.container.handler.Request.Method.PUT; +import static org.junit.Assert.assertEquals; + /** * @author jonmv */ -public class UserApiTest extends ControllerContainerTest { +public class UserApiTest extends ControllerContainerCloudTest { private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/"; @Test - @Ignore // TODO set up separate services.xlm for this kind of controller and unit test there. - public void testUserApi() { - ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles); + public void testUserManagement() { + ContainerTester tester = new ContainerTester(container, responseFiles); + assertEquals(SystemName.Public, tester.controller().system()); + Roles roles = new Roles(tester.controller().system()); + Set<Role> operator = Set.of(roles.hostedOperator()); + ApplicationId id = ApplicationId.from("my-tenant", "my-app", "default"); + + + // GET at application/v4 root fails as it's not public read. + tester.assertResponse(request("/application/v4/"), + accessDenied, 403); + + // GET at application/v4/tenant succeeds for operators. + tester.assertResponse(request("/application/v4/tenant") + .roles(operator), + "[]"); + + // POST a tenant is not available to everyone. + tester.assertResponse(request("/application/v4/tenant/my-tenant", POST) + .data("{\"token\":\"hello\"}"), + accessDenied, 403); + + // POST a tenant is available to operators. + tester.assertResponse(request("/application/v4/tenant/my-tenant", POST) + .roles(operator) + .user("owner@tenant") + .data("{\"token\":\"hello\"}"), + new File("tenant-without-applications.json")); + + // PUT a tenant is not available to anyone. + tester.assertResponse(request("/application/v4/user/", PUT) + .roles(operator), + "{\"error-code\":\"FORBIDDEN\",\"message\":\"Not authenticated or not a user.\"}", 403); + + // GET at user/v1 root fails as no access control is defined there. + tester.assertResponse(request("/user/v1/"), + accessDenied, 403); + + // POST a hosted operator role is not allowed. + tester.assertResponse(request("/user/v1/tenant/my-tenant", POST) + .roles(Set.of(roles.tenantOwner(id.tenant()))) + .data("{\"user\":\"evil@evil\",\"roleName\":\"hostedOperator\"}"), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Malformed or illegal role name 'hostedOperator'.\"}", 400); + + // POST a tenant operator is available to the tenant owner. + tester.assertResponse(request("/user/v1/tenant/my-tenant", POST) + .roles(Set.of(roles.tenantOwner(id.tenant()))) + .data("{\"user\":\"operator@tenant\",\"roleName\":\"tenantOperator\"}"), + "{\"message\":\"user 'operator@tenant' is now a member of role 'tenantOperator' of 'my-tenant'\"}"); + + // POST a tenant admin is not available to a tenant operator. + tester.assertResponse(request("/user/v1/tenant/my-tenant", POST) + .roles(Set.of(roles.tenantOperator(id.tenant()))) + .data("{\"user\":\"admin@tenant\",\"roleName\":\"tenantAdmin\"}"), + accessDenied, 403); + + // POST an application admin for a non-existent application fails. + tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", POST) + .roles(Set.of(roles.tenantOwner(TenantName.from("my-tenant")))) + .data("{\"user\":\"admin@app\",\"roleName\":\"applicationAdmin\"}"), + "{\"error-code\":\"INTERNAL_SERVER_ERROR\",\"message\":\"NullPointerException\"}", 500); + + // POST an application is allowed for a tenant operator. + tester.assertResponse(request("/application/v4/tenant/my-tenant/application/my-app", POST) + .user("operator@tenant") + .roles(Set.of(roles.tenantOperator(id.tenant()))), + new File("application-created.json")); + + // POST an application is not allowed under a different tenant. + tester.assertResponse(request("/application/v4/tenant/other-tenant/application/my-app", POST) + .roles(Set.of(roles.tenantOperator(id.tenant()))), + accessDenied, 403); + + // POST an application role is allowed for a tenant admin. + tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", POST) + .roles(Set.of(roles.tenantAdmin(id.tenant()))) + .data("{\"user\":\"reader@app\",\"roleName\":\"applicationReader\"}"), + "{\"message\":\"user 'reader@app' is now a member of role 'applicationReader' of 'my-app' owned by 'my-tenant'\"}"); + + // POST a tenant role is not allowed to an application. + tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", POST) + .roles(Set.of(roles.hostedOperator())) + .data("{\"user\":\"reader@app\",\"roleName\":\"tenantOperator\"}"), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Malformed or illegal role name 'tenantOperator'.\"}", 400); + + // GET tenant role information is available to application readers. + tester.assertResponse(request("/user/v1/tenant/my-tenant") + .roles(Set.of(roles.applicationReader(id.tenant(), id.application()))), + new File("tenant-roles.json")); + + // GET application role information is available to tenant operators. + tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app") + .roles(Set.of(roles.tenantOperator(id.tenant()))), + new File("application-roles.json")); + + // DELETE an application role is allowed for an application admin. + tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", DELETE) + .roles(Set.of(roles.applicationAdmin(id.tenant(), id.application()))) + .data("{\"user\":\"operator@tenant\",\"roleName\":\"applicationAdmin\"}"), + "{\"message\":\"user 'operator@tenant' is no longer a member of role 'applicationAdmin' of 'my-app' owned by 'my-tenant'\"}"); + + // DELETE an application is available to application admins. + tester.assertResponse(request("/application/v4/tenant/my-tenant/application/my-app", DELETE) + .roles(Set.of(roles.applicationAdmin(id.tenant(), id.application()))), + ""); + + // DELETE a tenant role is available to tenant admins. + tester.assertResponse(request("/user/v1/tenant/my-tenant", DELETE) + .roles(Set.of(roles.tenantAdmin(id.tenant()))) + .data("{\"user\":\"operator@tenant\",\"roleName\":\"tenantOperator\"}"), + "{\"message\":\"user 'operator@tenant' is no longer a member of role 'tenantOperator' of 'my-tenant'\"}"); + + // DELETE the last tenant owner is not allowed. + tester.assertResponse(request("/user/v1/tenant/my-tenant", DELETE) + .roles(operator) + .data("{\"user\":\"owner@tenant\",\"roleName\":\"tenantOwner\"}"), + "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Can't remove the last owner of a tenant.\"}", 400); - tester.assertResponse(authenticatedRequest("http://localhost:8080/user/v1/"), - "{\"error-code\":\"NOT_FOUND\",\"message\":\"No 'GET' handler at '/user/v1/'\"}", 404); + // DELETE the tenant is available to the tenant owner. + tester.assertResponse(request("/application/v4/tenant/my-tenant", DELETE) + .roles(Set.of(roles.tenantOwner(id.tenant()))), + new File("tenant-without-applications.json")); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-created.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-created.json new file mode 100644 index 00000000000..2a779f0ee55 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-created.json @@ -0,0 +1,6 @@ +{ + "tenant": "my-tenant", + "application": "my-app", + "instance": "default", + "url": "http://localhost:8080/application/v4/tenant/my-tenant/application/my-app" +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-roles.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-roles.json new file mode 100644 index 00000000000..e6ecf86c4ec --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/application-roles.json @@ -0,0 +1,54 @@ +{ + "tenant": "my-tenant", + "application": "my-app", + "roleNames": [ + "applicationAdmin", + "applicationOperator", + "applicationDeveloper", + "applicationReader" + ], + "users": [ + { + "name": "reader@app", + "roles": { + "applicationAdmin": { + "explicit": false, + "implied": false + }, + "applicationOperator": { + "explicit": false, + "implied": false + }, + "applicationDeveloper": { + "explicit": false, + "implied": false + }, + "applicationReader": { + "explicit": true, + "implied": false + } + } + }, + { + "name": "operator@tenant", + "roles": { + "applicationAdmin": { + "explicit": true, + "implied": false + }, + "applicationOperator": { + "explicit": false, + "implied": true + }, + "applicationDeveloper": { + "explicit": false, + "implied": true + }, + "applicationReader": { + "explicit": false, + "implied": true + } + } + } + ] +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-roles.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-roles.json new file mode 100644 index 00000000000..46f715b481b --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-roles.json @@ -0,0 +1,44 @@ +{ + "tenant": "my-tenant", + "roleNames": [ + "tenantOwner", + "tenantAdmin", + "tenantOperator" + ], + "users": [ + { + "name": "operator@tenant", + "roles": { + "tenantOwner": { + "explicit": false, + "implied": false + }, + "tenantAdmin": { + "explicit": false, + "implied": false + }, + "tenantOperator": { + "explicit": true, + "implied": false + } + } + }, + { + "name": "owner@tenant", + "roles": { + "tenantOwner": { + "explicit": true, + "implied": false + }, + "tenantAdmin": { + "explicit": false, + "implied": true + }, + "tenantOperator": { + "explicit": false, + "implied": true + } + } + } + ] +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json new file mode 100644 index 00000000000..e4ca5a02446 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-without-applications.json @@ -0,0 +1,5 @@ +{ + "tenant": "my-tenant", + "type": "CLOUD", + "applications": [] +} |