Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for Issue #29 #30

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions registry-ws/doc/identity_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@

| URL | Method | Success | Description |
| --- | --- | --- | --- |
| `/user/login` | `GET` | `{USER}` | Logs in a user with HTTP Basic Auth returning the user in the response |
| `/user/changePassword`| `PUT` | `204` | Allows user to change its own password using HTTP Basic Auth |
| `/identity/login` | `GET` | `{USER}` | Logs in a user with HTTP Basic Auth returning the user in the response |
| `/identity/changePassword`| `PUT` | `204` | Allows user to change its own password using HTTP Basic Auth |

#### Administrative and Trusted application endpoints
| URL | Method | Success | Description |
| --- | --- | --- | --- |
| `admin/user` | `POST` | `201` | Creates a user. Verifies [required fields](#create-user-fields) and returns `422` if the user can not be created. The appkey must be provided as `x-gbif-user` |
| `admin/user/{username}` | `GET` | `{USER}` | Gets the user role is authorised to view the user (e.g. enable admins to edit account details) |
| `admin/user/{username}` | `PUT` | `204` | Updates the user. Verifies [fields](#update-user-fields) and returns `422` if the user can not be updated. |
| `admin/user/confirm` | `POST` | `201` `{USER}` | Confirms that the user have access to that mail. The target user must be provided as `x-gbif-user` and the `confirmationKey` in the post body (as JSON). |
| `admin/user/resetPassword` | `POST` | `204` | Send user a mail with link to reset password. The target user (userName or email) must be provided as `x-gbif-user`. |
| `admin/user/confirmationKeyValid?confirmationKey={confirmationKey}` | `GET` | `204` | Utility for the web app to determine if the confirmationKey is the currently valid one for the user. Returns `204` if so (app will then present the new password form) or `401` if the confirmationKey is not considered authorized to change the password. The target user must be provided as `x-gbif-user`. |
| `admin/user/updatePassword` | `POST` | `201` `{USER}`| Updates the password for the user by accepting the `confirmationKey` and `password` in the post body (as JSON). Returns `401` if the token is not authorized to change the password. Deletes all user tokens and return a new login token to set as cookie. The target user must be provided as `x-gbif-user`. |
| `admin/identity/user` | `POST` | `201` | Creates a user. Verifies [required fields](#create-user-fields) and returns `422` if the user can not be created. The appkey must be provided as `x-gbif-user` |
| `admin/identity/user/{username}` | `GET` | `{USER}` | Gets the user role is authorised to view the user (e.g. enable admins to edit account details) |
| `admin/identity/user/{username}` | `PUT` | `204` | Updates the user. Verifies [fields](#update-user-fields) and returns `422` if the user can not be updated. |
| `admin/identity/confirm` | `POST` | `201` `{USER}` | Confirms that the user have access to that mail. The target user must be provided as `x-gbif-user` and the `confirmationKey` in the post body (as JSON). |
| `admin/identity/find` | `GET` | `200` `{USER}` | Find a user using systemSettings provided as query parameters. |
| `admin/identity/resetPassword` | `POST` | `204` | Send user a mail with link to reset password. The target user (userName or email) must be provided as `x-gbif-user`. |
| `admin/identity/confirmationKeyValid?confirmationKey={confirmationKey}` | `GET` | `204` | Utility for the web app to determine if the confirmationKey is the currently valid one for the user. Returns `204` if so (app will then present the new password form) or `401` if the confirmationKey is not considered authorized to change the password. The target user must be provided as `x-gbif-user`. |
| `admin/identity/updatePassword` | `POST` | `201` `{USER}`| Updates the password for the user by accepting the `confirmationKey` and `password` in the post body (as JSON). Returns `401` if the token is not authorized to change the password. Deletes all user tokens and return a new login token to set as cookie. The target user must be provided as `x-gbif-user`. |

### Create user fields

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@
* - Application itself (APP_ROLE). All applications with a valid appKey that is also present in the appKey whitelist.
* See {@link org.gbif.ws.server.filter.AppIdentityFilter}.
*/
@Path("admin/user")
@Path("admin/identity")
@Produces({MediaType.APPLICATION_JSON, ExtraMediaTypes.APPLICATION_JAVASCRIPT})
@Consumes(MediaType.APPLICATION_JSON)
@Singleton
public class UserManagementResource {

//filters roles that are deprecated
private static final List<UserRole> USER_ROLES = Arrays.stream(UserRole.values()).filter(r ->
!AnnotationUtils.isFieldDeprecated(UserRole.class, r.name())).collect(Collectors.toList());
Expand Down Expand Up @@ -115,7 +115,7 @@ public List<UserRole> listRoles() {
*/
@GET
@RolesAllowed({ADMIN_ROLE, APP_ROLE})
@Path("/{username}")
@Path("user/{username}")
public UserAdminView getUser(@PathParam("username") String username, @Context SecurityContext securityContext,
@Context HttpServletRequest request) {

Expand Down Expand Up @@ -152,7 +152,7 @@ public UserAdminView getUserBySystemSetting(@Context HttpServletRequest request)
*/
@POST
@RolesAllowed(APP_ROLE)
@Path("/")
@Path("user")
public Response create(@Context SecurityContext securityContext, @Context HttpServletRequest request, UserCreation user) {

int returnStatusCode = Response.Status.CREATED.getStatusCode();
Expand Down Expand Up @@ -181,7 +181,7 @@ public Response create(@Context SecurityContext securityContext, @Context HttpSe
*/
@PUT
@RolesAllowed({ADMIN_ROLE, APP_ROLE})
@Path("/{username}")
@Path("user/{username}")
public Response update(@PathParam("username") String username, UserUpdate userUpdate,
@Context SecurityContext securityContext, @Context HttpServletRequest request) {

Expand Down Expand Up @@ -243,7 +243,7 @@ public Response confirmChallengeCode(@Context SecurityContext securityContext, @
@DELETE
@RolesAllowed(ADMIN_ROLE)
@Consumes(MediaType.WILDCARD)
@Path("/{userKey}")
@Path("user/{userKey}")
public Response delete(@PathParam("userKey") int userKey) {
identityService.delete(userKey);
return Response.noContent().build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
* - keys (user id) are not considered public, therefore the username is used as key
* - In order to strictly control the data that is exposed this class uses "view models" (e.g. {@link LoggedUser})
*/
@Path("user")
@Path("identity")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Singleton
Expand Down
6 changes: 3 additions & 3 deletions registry-ws/src/main/webapp/web/app/user/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ angular.module('user', [
}])

.controller('UserSearchCtrl', function ($scope, $state, Restangular, DEFAULT_PAGE_SIZE) {
var user = Restangular.all("admin/user/search");
var user = Restangular.all("admin/identity/search");
$scope.search = function(q) {
user.getList({q:q, limit:DEFAULT_PAGE_SIZE}).then(function(data) {
$scope.resultsCount = data.count;
Expand All @@ -59,14 +59,14 @@ angular.module('user', [
var key = $stateParams.userName;

var load = function () {
Restangular.one('admin/user', key).get()
Restangular.one('admin/identity/user', key).get()
.then(function (user) {
user.key = user.userName;
$scope.user = user;
return user;
})
.then(function (user) {
Restangular.all("admin/user/roles").getList().then(function (data) {
Restangular.all("admin/identity/roles").getList().then(function (data) {
$scope.roles = data;
$scope.user_roles = {};
_.each($scope.roles, function (element, index, list) {
Expand Down
2 changes: 1 addition & 1 deletion registry-ws/src/test/java/org/gbif/registry/UserIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ protected GbifAuthService getAuthService() {

@Override
protected String getResourcePath() {
return UserTestFixture.USER_RESOURCE_PATH;
return UserTestFixture.IDENTITY_RESOURCE_PATH;
}

@Override
Expand Down
22 changes: 11 additions & 11 deletions registry-ws/src/test/java/org/gbif/registry/UserManagementIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import org.gbif.ws.security.GbifAuthService;

import java.util.UUID;
import java.util.function.Function;
import javax.ws.rs.core.Response;

import com.google.common.collect.ImmutableMap;
Expand All @@ -26,6 +25,10 @@
import org.junit.Test;

import static org.gbif.registry.ws.fixtures.UserTestFixture.ALTERNATE_USERNAME;
import static org.gbif.registry.ws.fixtures.UserTestFixture.IDENTITY_ADMIN_RESOURCE_PATH;
import static org.gbif.registry.ws.fixtures.UserTestFixture.RESET_PASSWORD_PATH;
import static org.gbif.registry.ws.fixtures.UserTestFixture.UPDATE_PASSWORD_PATH;
import static org.gbif.registry.ws.fixtures.UserTestFixture.USER_RESOURCE_PATH;
import static org.gbif.registry.ws.util.AssertHttpResponse.assertResponse;

import static junit.framework.TestCase.assertEquals;
Expand All @@ -41,10 +44,7 @@
public class UserManagementIT extends PlainAPIBaseIT {

private static final String CHANGED_PASSWORD = "123456";
private static final String RESET_PASSWORD_PATH = "resetPassword";
private static final String UPDATE_PASSWORD_PATH = "updatePassword";

private static final String RESOURCE_PATH = "admin/user";
private GbifAuthService gbifAuthService = GbifAuthService.singleKeyAuthService(
TestConstants.IT_APP_KEY, TestConstants.IT_APP_SECRET);

Expand All @@ -70,7 +70,7 @@ public void testCreateUser() {
String newUserName = ALTERNATE_USERNAME;

ClientResponse cr = postSignedRequest(TestConstants.IT_APP_KEY,
UserTestFixture.generateUser(newUserName), Function.identity());
UserTestFixture.generateUser(newUserName), (uriBldr) -> uriBldr.path(USER_RESOURCE_PATH));
assertResponse(Response.Status.CREATED, cr);

//test we can't login (challengeCode not confirmed)
Expand All @@ -97,7 +97,7 @@ public void testCreateUserNonWhiteListAppKey() {
GbifAuthService gbifAuthServiceKey2 = GbifAuthService.singleKeyAuthService(
TestConstants.IT_APP_KEY2, TestConstants.IT_APP_SECRET2);
ClientResponse cr = postSignedRequest(gbifAuthServiceKey2, TestConstants.IT_APP_KEY2,
UserTestFixture.generateUser(newUserName), Function.identity());
UserTestFixture.generateUser(newUserName), (uriBldr) -> uriBldr.path(USER_RESOURCE_PATH));
//it will authenticate since the appKey is valid but it won't get the APP role
assertResponse(Response.Status.FORBIDDEN, cr);
}
Expand Down Expand Up @@ -167,7 +167,7 @@ public void getUserFromAdmin() {
GbifUser testUser = userTestFixture.prepareUser();
GbifUser createdUser = userMapper.get(testUser.getUserName());

ClientResponse cr = getWithSignedRequest(TestConstants.IT_APP_KEY, uriBldr -> uriBldr.path(testUser.getUserName()));
ClientResponse cr = getWithSignedRequest(TestConstants.IT_APP_KEY, uriBldr -> uriBldr.path(USER_RESOURCE_PATH).path(testUser.getUserName()));
assertResponse(Response.Status.OK, cr);

assertEquals(createdUser.getKey(), cr.getEntity(UserAdminView.class).getUser().getKey());
Expand Down Expand Up @@ -195,7 +195,7 @@ public void testUpdateUser() {
assertResponse(Response.Status.OK, cr);

testUser.setFirstName(newUserFirstName);
cr = putWithSignedRequest(TestConstants.IT_APP_KEY, testUser, uriBldr -> uriBldr.path(testUser.getUserName()));
cr = putWithSignedRequest(TestConstants.IT_APP_KEY, testUser, uriBldr -> uriBldr.path(USER_RESOURCE_PATH).path(testUser.getUserName()));
assertResponse(Response.Status.NO_CONTENT, cr);

//load user directly from the database
Expand All @@ -209,12 +209,12 @@ public void testUpdateUser() {

//update user2 using email from user1
testUser2.setEmail(testUser.getEmail());
cr = putWithSignedRequest(TestConstants.IT_APP_KEY, testUser2, uriBldr -> uriBldr.path(ALTERNATE_USERNAME));
cr = putWithSignedRequest(TestConstants.IT_APP_KEY, testUser2, uriBldr -> uriBldr.path(USER_RESOURCE_PATH).path(ALTERNATE_USERNAME));
assertEquals(GbifResponseStatus.UNPROCESSABLE_ENTITY.getStatus(), cr.getStatus());
assertEquals(ModelMutationError.EMAIL_ALREADY_IN_USE, cr.getEntity(UserModelMutationResult.class).getError());

testUser2.setEmail("[email protected]");
cr = putWithSignedRequest(TestConstants.IT_APP_KEY, testUser2, uriBldr -> uriBldr.path(ALTERNATE_USERNAME));
cr = putWithSignedRequest(TestConstants.IT_APP_KEY, testUser2, uriBldr -> uriBldr.path(USER_RESOURCE_PATH).path(ALTERNATE_USERNAME));
assertResponse(Response.Status.NO_CONTENT, cr);
}

Expand All @@ -225,7 +225,7 @@ protected GbifAuthService getAuthService() {

@Override
protected String getResourcePath() {
return RESOURCE_PATH;
return IDENTITY_ADMIN_RESOURCE_PATH;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import com.sun.jersey.api.json.JSONConfiguration;

import static org.gbif.registry.ws.fixtures.UserTestFixture.USER_RESOURCE_PATH;
import static org.gbif.registry.ws.fixtures.UserTestFixture.IDENTITY_RESOURCE_PATH;

/**
* Generates and offer utilities related to authenticated client in the context of testing.
Expand Down Expand Up @@ -66,7 +66,7 @@ public static Client buildAuthenticatedAdmin() {
*/
public ClientResponse login(String username, String password) {
return generateAuthenticatedClient(username, password,
USER_RESOURCE_PATH).get(wr -> wr.path("login"));
IDENTITY_RESOURCE_PATH).get(wr -> wr.path("login"));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@
*/
public class UserTestFixture {

public static final String IDENTITY_RESOURCE_PATH = "identity";
public static final String IDENTITY_ADMIN_RESOURCE_PATH = "admin/identity";
public static final String USER_RESOURCE_PATH = "user";

public static final String RESET_PASSWORD_PATH = "resetPassword";
public static final String UPDATE_PASSWORD_PATH = "updatePassword";

public static final String USERNAME = "user_12";
public static final String ALTERNATE_USERNAME = "user_13";
public static final String PASSWORD = "password";
Expand Down