Java tutorial
/* * Licensed 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.opendatakit.api.admin; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.opendatakit.aggregate.odktables.rest.ApiConstants; import org.opendatakit.api.users.entity.RoleDescription; import org.opendatakit.api.users.entity.UserEntity; import org.opendatakit.constants.BasicConsts; import org.opendatakit.constants.ErrorConsts; import org.opendatakit.constants.SecurityConsts; import org.opendatakit.context.CallingContext; import org.opendatakit.persistence.Datastore; import org.opendatakit.persistence.client.exception.DatastoreFailureException; import org.opendatakit.persistence.exception.ODKDatastoreException; import org.opendatakit.persistence.table.GrantedAuthorityHierarchyTable; import org.opendatakit.persistence.table.RegisteredUsersTable; import org.opendatakit.persistence.table.UserGrantedAuthority; import org.opendatakit.security.User; import org.opendatakit.security.client.UserSecurityInfo; import org.opendatakit.security.client.exception.AccessDeniedException; import org.opendatakit.security.common.EmailParser; import org.opendatakit.security.common.GrantedAuthorityName; import org.opendatakit.security.server.SecurityServiceUtil; import org.opendatakit.utils.SecurityUtils; import org.opendatakit.utils.UserRoleUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.Authorization; @Api(value = "/admin", description = "ODK User Admin API", authorizations = { @Authorization(value = "basicAuth") }) @Path("/admin") @Secured({ "ROLE_SITE_ACCESS_ADMIN" }) public class UserAdminService { @Autowired private CallingContext callingContext; private static final Log logger = LogFactory.getLog(UserAdminService.class); private static final ObjectMapper mapper = new ObjectMapper(); @ApiOperation(value = "Get a list of all users with role information.") @GET @Path("users") @Produces({ MediaType.APPLICATION_JSON, ApiConstants.MEDIA_TEXT_XML_UTF8, ApiConstants.MEDIA_APPLICATION_XML_UTF8 }) public Response getList() throws IOException { return internalGetList(callingContext); } @ApiOperation(value = "Get information and roles for a single user by username.") @GET @Path("users/username:{username}") @Produces({ MediaType.APPLICATION_JSON, ApiConstants.MEDIA_TEXT_XML_UTF8, ApiConstants.MEDIA_APPLICATION_XML_UTF8 }) public Response getUser(@PathParam("username") String username) { UserEntity resultUserEntity = null; try { RegisteredUsersTable user = RegisteredUsersTable.getUserByUsername(username, callingContext.getUserService(), callingContext.getDatastore()); if (user == null) { return Response.status(Status.NOT_FOUND).entity("User not found.") .header(ApiConstants.OPEN_DATA_KIT_VERSION_HEADER, ApiConstants.OPEN_DATA_KIT_VERSION) .header("Access-Control-Allow-Origin", "*") .header("Access-Control-Allow-Credentials", "true").build(); } UserSecurityInfo userSecurityInfo = new UserSecurityInfo(user.getUsername(), user.getFullName(), user.getEmail(), UserSecurityInfo.UserType.REGISTERED, user.getOfficeId()); SecurityServiceUtil.setAuthenticationLists(userSecurityInfo, user.getUri(), callingContext); resultUserEntity = UserRoleUtils.getEntityFromUserSecurityInfo(userSecurityInfo); } catch (ODKDatastoreException e) { logger.error("Error retrieving ", e); throw new WebApplicationException(ErrorConsts.PERSISTENCE_LAYER_PROBLEM + "\n" + e.toString(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } return Response.ok().entity(resultUserEntity).encoding(BasicConsts.UTF8_ENCODE) .type(MediaType.APPLICATION_JSON) .header(ApiConstants.OPEN_DATA_KIT_VERSION_HEADER, ApiConstants.OPEN_DATA_KIT_VERSION) .header("Access-Control-Allow-Origin", "*").header("Access-Control-Allow-Credentials", "true") .build(); } @ApiOperation(value = "Set a password in cleartext. Probably a good idea to disable this endpoint in production.") @POST @Path("users/username:{username}/password") @Consumes({ MediaType.APPLICATION_JSON, ApiConstants.MEDIA_TEXT_XML_UTF8, ApiConstants.MEDIA_APPLICATION_XML_UTF8 }) @Secured({ "ROLE_SITE_ACCESS_ADMIN" }) public Response setUserPassword(@PathParam("username") String username, String password) throws AccessDeniedException, DatastoreFailureException { SecurityUtils.updateCleartextPassword(callingContext, username, password); return Response.status(Status.OK) .header(ApiConstants.OPEN_DATA_KIT_VERSION_HEADER, ApiConstants.OPEN_DATA_KIT_VERSION) .header("Access-Control-Allow-Origin", "*").header("Access-Control-Allow-Credentials", "true") .build(); } @ApiOperation(value = "Set a password using digest hash.") @POST @Path("users/username:{username}/password/digest") @Consumes({ MediaType.APPLICATION_JSON, ApiConstants.MEDIA_TEXT_XML_UTF8, ApiConstants.MEDIA_APPLICATION_XML_UTF8 }) @Secured({ "ROLE_SITE_ACCESS_ADMIN" }) public Response setUserDigestPassword(@PathParam("username") String username, String password) throws AccessDeniedException, DatastoreFailureException { SecurityUtils.updateDigestPassword(callingContext, username, password); return Response.status(Status.OK) .header(ApiConstants.OPEN_DATA_KIT_VERSION_HEADER, ApiConstants.OPEN_DATA_KIT_VERSION) .header("Access-Control-Allow-Origin", "*").header("Access-Control-Allow-Credentials", "true") .build(); } @ApiOperation(value = "Add or update user to database. Uses user_id field as unique key which determines if user is created or updated.") @POST @Path("users") @Produces({ MediaType.APPLICATION_JSON, ApiConstants.MEDIA_TEXT_XML_UTF8, ApiConstants.MEDIA_APPLICATION_XML_UTF8 }) @Consumes({ MediaType.APPLICATION_JSON, ApiConstants.MEDIA_TEXT_XML_UTF8, ApiConstants.MEDIA_APPLICATION_XML_UTF8 }) @Secured({ "ROLE_SITE_ACCESS_ADMIN" }) public Response putUser(UserEntity userEntity) throws AccessDeniedException, DatastoreFailureException { try { String fullName = userEntity.getFullName(); String officeId = userEntity.getOfficeId(); String userId = userEntity.getUserId(); String username = null; String email = null; if (userId.toLowerCase().startsWith(EmailParser.K_MAILTO)) { email = userId.substring(EmailParser.K_MAILTO.length()); } if (userId.toLowerCase().startsWith(SecurityConsts.USERNAME_COLON)) { username = userId.substring(SecurityConsts.USERNAME_COLON.length()); } List<String> roles = userEntity.getRoles(); UserSecurityInfo userSecurityInfo = new UserSecurityInfo(username, fullName, email, UserSecurityInfo.UserType.REGISTERED, officeId); RegisteredUsersTable user = RegisteredUsersTable.assertActiveUserByUserSecurityInfo(userSecurityInfo, callingContext); UserGrantedAuthority.assertUserGrantedAuthorities(user.getUri(), roles, callingContext); UserSecurityInfo resultUserSecurityInfo = new UserSecurityInfo(user.getUsername(), user.getFullName(), user.getEmail(), UserSecurityInfo.UserType.REGISTERED, user.getOfficeId()); SecurityServiceUtil.setAuthenticationLists(resultUserSecurityInfo, user.getUri(), callingContext); UserEntity resultUserEntity = UserRoleUtils.getEntityFromUserSecurityInfo(resultUserSecurityInfo); String eTag = Integer.toHexString(resultUserEntity.hashCode()); return Response.status(Status.CREATED).entity(resultUserEntity).header(HttpHeaders.ETAG, eTag) .header(ApiConstants.OPEN_DATA_KIT_VERSION_HEADER, ApiConstants.OPEN_DATA_KIT_VERSION) .header("Access-Control-Allow-Origin", "*").header("Access-Control-Allow-Credentials", "true") .build(); } catch (ODKDatastoreException e) { logger.error("Error creating/updating user", e); throw new WebApplicationException(ErrorConsts.PERSISTENCE_LAYER_PROBLEM + "\n" + e.toString(), HttpServletResponse.SC_BAD_REQUEST); } } @ApiOperation(value = "Add or update anonymous user. Anonymous user does not have a _registered_users entry in the database and is 'special' so we handle it separately.") @POST @Path("users/anonymous") @Produces({ MediaType.APPLICATION_JSON, ApiConstants.MEDIA_TEXT_XML_UTF8, ApiConstants.MEDIA_APPLICATION_XML_UTF8 }) @Consumes({ MediaType.APPLICATION_JSON, ApiConstants.MEDIA_TEXT_XML_UTF8, ApiConstants.MEDIA_APPLICATION_XML_UTF8 }) @Secured({ "ROLE_SITE_ACCESS_ADMIN" }) public Response putAnonymousUser(UserEntity userEntity) throws AccessDeniedException, DatastoreFailureException { try { GrantedAuthority anonAuth = new SimpleGrantedAuthority(GrantedAuthorityName.USER_IS_ANONYMOUS.name()); List<String> roles = userEntity.getRoles(); List<String> anonGrantStrings = new ArrayList<String>(); for (String role : roles) { // Whitelist only allowed roles. if (GrantedAuthorityName.ROLE_USER.name().equals(role) || GrantedAuthorityName.ROLE_DATA_COLLECTOR.name().equals(role) || GrantedAuthorityName.ROLE_DATA_VIEWER.name().equals(role) || GrantedAuthorityName.ROLE_DATA_OWNER.name().equals(role) || GrantedAuthorityName.ROLE_SYNCHRONIZE_TABLES.name().equals(role) || GrantedAuthorityName.ROLE_SUPER_USER_TABLES.name().equals(role) || GrantedAuthorityName.ROLE_ADMINISTER_TABLES.name().equals(role)) { anonGrantStrings.add(role); } } GrantedAuthorityHierarchyTable.assertGrantedAuthorityHierarchy(anonAuth, anonGrantStrings, callingContext); return Response.status(Status.CREATED) .header(ApiConstants.OPEN_DATA_KIT_VERSION_HEADER, ApiConstants.OPEN_DATA_KIT_VERSION) .header("Access-Control-Allow-Origin", "*").header("Access-Control-Allow-Credentials", "true") .build(); } catch (ODKDatastoreException e) { logger.error("Error updating anonymous user", e); throw new WebApplicationException(ErrorConsts.PERSISTENCE_LAYER_PROBLEM + "\n" + e.toString(), HttpServletResponse.SC_BAD_REQUEST); } } @ApiOperation(value = "Delete user by username.") @DELETE @Path("users/username:{username}") @Secured({ "ROLE_SITE_ACCESS_ADMIN" }) public Response deleteUser(@PathParam("username") String username) throws IOException, DatastoreFailureException { Datastore ds = callingContext.getDatastore(); User user = callingContext.getCurrentUser(); try { RegisteredUsersTable deleteUser = RegisteredUsersTable.getUserByUsername(username, callingContext.getUserService(), callingContext.getDatastore()); if (deleteUser != null) { UserGrantedAuthority.deleteGrantedAuthoritiesForUser(deleteUser.getUri(), callingContext.getUserService(), callingContext.getDatastore(), user); ds.deleteEntity(deleteUser.getEntityKey(), user); } } catch (ODKDatastoreException e) { logger.error(e); throw new DatastoreFailureException(e); } return Response.status(Status.NO_CONTENT) .header(ApiConstants.OPEN_DATA_KIT_VERSION_HEADER, ApiConstants.OPEN_DATA_KIT_VERSION) .header("Access-Control-Allow-Origin", "*").header("Access-Control-Allow-Credentials", "true") .build(); } public static Response internalGetList(CallingContext callingContext) throws JsonProcessingException { ArrayList<UserEntity> listOfUsers = new ArrayList<UserEntity>(); UserEntity userEntity; try { ArrayList<UserSecurityInfo> allUsers = SecurityServiceUtil.getAllUsers(true, callingContext); for (UserSecurityInfo userSecurityInfo : allUsers) { userEntity = UserRoleUtils.getEntityFromUserSecurityInfo(userSecurityInfo); listOfUsers.add(userEntity); } } catch (DatastoreFailureException e) { logger.error("Retrieving users persistence error: " + e.toString(), e); throw new WebApplicationException(ErrorConsts.PERSISTENCE_LAYER_PROBLEM + "\n" + e.toString(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } catch (AccessDeniedException e) { logger.error("Retrieving users access denied error: " + e.toString(), e); throw new WebApplicationException(ErrorConsts.PERSISTENCE_LAYER_PROBLEM + "\n" + e.toString(), HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } // Need to set host header? original has // resp.addHeader(HttpHeaders.HOST, cc.getServerURL()); return Response.ok(mapper.writeValueAsString(listOfUsers)).encoding(BasicConsts.UTF8_ENCODE) .type(MediaType.APPLICATION_JSON) .header(ApiConstants.OPEN_DATA_KIT_VERSION_HEADER, ApiConstants.OPEN_DATA_KIT_VERSION) .header("Access-Control-Allow-Origin", "*").header("Access-Control-Allow-Credentials", "true") .build(); } }