Java tutorial
/* * Copyright 2017 Danish Maritime Authority. * * 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 net.maritimecloud.identityregistry.controllers; import io.swagger.annotations.ApiOperation; import net.maritimecloud.identityregistry.exception.DuplicatedKeycloakEntry; import net.maritimecloud.identityregistry.exception.McBasicRestException; import net.maritimecloud.identityregistry.model.data.CertificateRevocation; import net.maritimecloud.identityregistry.model.data.PemCertificate; import net.maritimecloud.identityregistry.model.database.Certificate; import net.maritimecloud.identityregistry.model.database.CertificateModel; import net.maritimecloud.identityregistry.model.database.Organization; import net.maritimecloud.identityregistry.model.database.Role; import net.maritimecloud.identityregistry.model.database.entities.User; import net.maritimecloud.identityregistry.services.EntityService; import net.maritimecloud.identityregistry.services.RoleService; import net.maritimecloud.identityregistry.utils.AccessControlUtil; import net.maritimecloud.identityregistry.utils.EmailUtil; import net.maritimecloud.identityregistry.utils.KeycloakAdminUtil; import net.maritimecloud.identityregistry.utils.MCIdRegConstants; import net.maritimecloud.identityregistry.utils.MrnUtil; import net.maritimecloud.identityregistry.utils.PasswordUtil; import net.maritimecloud.identityregistry.utils.ValidateUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import java.io.IOException; @RestController public class UserController extends EntityController<User> { // Data that identifies the User sync'er @Value("${net.maritimecloud.idreg.user-sync.c}") private String userSyncC; @Value("${net.maritimecloud.idreg.user-sync.o}") private String userSyncO; @Value("${net.maritimecloud.idreg.user-sync.ou}") private String userSyncOU; @Value("${net.maritimecloud.idreg.user-sync.mrn}") private String userSyncMRN; @Autowired public void setUserService(EntityService<User> userService) { this.entityService = userService; } @Autowired private RoleService roleService; @Autowired private KeycloakAdminUtil keycloakAU; @Autowired private EmailUtil emailUtil; /** * Creates a new User * * @return a reply... * @throws McBasicRestException */ @RequestMapping(value = "/api/org/{orgMrn}/user", method = RequestMethod.POST, produces = "application/json;charset=UTF-8") @ResponseBody @PreAuthorize("hasRole('USER_ADMIN') and @accessControlUtil.hasAccessToOrg(#orgMrn)") public ResponseEntity<User> createUser(HttpServletRequest request, @PathVariable String orgMrn, @Valid @RequestBody User input, BindingResult bindingResult) throws McBasicRestException { ValidateUtil.hasErrors(bindingResult, request); Organization org = this.organizationService.getOrganizationByMrn(orgMrn); if (org != null) { // Check that the entity being created belongs to the organization if (!MrnUtil.getOrgShortNameFromOrgMrn(orgMrn) .equals(MrnUtil.getOrgShortNameFromEntityMrn(input.getMrn()))) { throw new McBasicRestException(HttpStatus.BAD_REQUEST, MCIdRegConstants.MISSING_RIGHTS, request.getServletPath()); } // If the organization doesn't have its own Identity Provider we create the user in a special keycloak instance if ("test-idp".equals(org.getFederationType()) && (org.getIdentityProviderAttributes() == null || org.getIdentityProviderAttributes().isEmpty())) { String password = PasswordUtil.generatePassword(); keycloakAU.init(KeycloakAdminUtil.USER_INSTANCE); try { keycloakAU.createUser(input.getMrn(), password, input.getFirstName(), input.getLastName(), input.getEmail(), orgMrn, input.getPermissions(), true); } catch (DuplicatedKeycloakEntry dke) { throw new McBasicRestException(HttpStatus.CONFLICT, dke.getErrorMessage(), request.getServletPath()); } catch (IOException e) { throw new McBasicRestException(HttpStatus.INTERNAL_SERVER_ERROR, MCIdRegConstants.ERROR_CREATING_KC_USER, request.getServletPath()); } // Send email to user with credentials emailUtil.sendUserCreatedEmail(input.getEmail(), input.getFirstName() + " " + input.getLastName(), input.getEmail(), password); } input.setIdOrganization(org.getId()); try { User newUser = this.entityService.save(input); return new ResponseEntity<>(newUser, HttpStatus.OK); } catch (DataIntegrityViolationException e) { // If save to DB failed, remove the user from keycloak if it was created. if ("test-idp".equals(org.getFederationType()) && (org.getIdentityProviderAttributes() == null || org.getIdentityProviderAttributes().isEmpty())) { keycloakAU.deleteUser(input.getEmail()); } throw new McBasicRestException(HttpStatus.CONFLICT, e.getRootCause().getMessage(), request.getServletPath()); } } else { throw new McBasicRestException(HttpStatus.NOT_FOUND, MCIdRegConstants.ORG_NOT_FOUND, request.getServletPath()); } } /** * Returns info about the user identified by the given ID * * @return a reply... * @throws McBasicRestException */ @RequestMapping(value = "/api/org/{orgMrn}/user/{userMrn}", method = RequestMethod.GET, produces = "application/json;charset=UTF-8") @ResponseBody @PreAuthorize("@accessControlUtil.hasAccessToOrg(#orgMrn)") public ResponseEntity<User> getUser(HttpServletRequest request, @PathVariable String orgMrn, @PathVariable String userMrn) throws McBasicRestException { return this.getEntity(request, orgMrn, userMrn); } /** * Updates a User * * @return a reply... * @throws McBasicRestException */ @RequestMapping(value = "/api/org/{orgMrn}/user/{userMrn}", method = RequestMethod.PUT) @ResponseBody @PreAuthorize("hasRole('USER_ADMIN') and @accessControlUtil.hasAccessToOrg(#orgMrn)") public ResponseEntity<?> updateUser(HttpServletRequest request, @PathVariable String orgMrn, @PathVariable String userMrn, @Valid @RequestBody User input, BindingResult bindingResult) throws McBasicRestException { ValidateUtil.hasErrors(bindingResult, request); if (!userMrn.equals(input.getMrn())) { throw new McBasicRestException(HttpStatus.BAD_REQUEST, MCIdRegConstants.URL_DATA_MISMATCH, request.getServletPath()); } Organization org = this.organizationService.getOrganizationByMrn(orgMrn); if (org != null) { // Check that the entity being updated belongs to the organization if (!MrnUtil.getOrgShortNameFromOrgMrn(orgMrn) .equals(MrnUtil.getOrgShortNameFromEntityMrn(input.getMrn()))) { throw new McBasicRestException(HttpStatus.BAD_REQUEST, MCIdRegConstants.MISSING_RIGHTS, request.getServletPath()); } User user = this.entityService.getByMrn(userMrn); if (user == null) { throw new McBasicRestException(HttpStatus.NOT_FOUND, MCIdRegConstants.USER_NOT_FOUND, request.getServletPath()); } if (!user.getMrn().equals(input.getMrn()) || user.getIdOrganization().compareTo(org.getId()) != 0) { throw new McBasicRestException(HttpStatus.BAD_REQUEST, MCIdRegConstants.URL_DATA_MISMATCH, request.getServletPath()); } // Update user in keycloak if created there. if ("test-idp".equals(org.getFederationType()) && (org.getIdentityProviderAttributes() == null || org.getIdentityProviderAttributes().isEmpty())) { keycloakAU.init(KeycloakAdminUtil.USER_INSTANCE); try { keycloakAU.updateUser(input.getMrn(), input.getFirstName(), input.getLastName(), input.getEmail(), input.getPermissions(), true); } catch (IOException e) { throw new McBasicRestException(HttpStatus.INTERNAL_SERVER_ERROR, MCIdRegConstants.ERROR_UPDATING_KC_USER, request.getServletPath()); } } input.selectiveCopyTo(user); this.entityService.save(user); return new ResponseEntity<>(HttpStatus.OK); } else { throw new McBasicRestException(HttpStatus.NOT_FOUND, MCIdRegConstants.ORG_NOT_FOUND, request.getServletPath()); } } /** * Deletes a User * * @return a reply... * @throws McBasicRestException */ @RequestMapping(value = "/api/org/{orgMrn}/user/{userMrn}", method = RequestMethod.DELETE) @ResponseBody @PreAuthorize("hasRole('USER_ADMIN') and @accessControlUtil.hasAccessToOrg(#orgMrn)") public ResponseEntity<?> deleteUser(HttpServletRequest request, @PathVariable String orgMrn, @PathVariable String userMrn) throws McBasicRestException { Organization org = this.organizationService.getOrganizationByMrn(orgMrn); if (org != null) { // Check that the entity being deleted belongs to the organization if (!MrnUtil.getOrgShortNameFromOrgMrn(orgMrn).equals(MrnUtil.getOrgShortNameFromEntityMrn(userMrn))) { throw new McBasicRestException(HttpStatus.BAD_REQUEST, MCIdRegConstants.MISSING_RIGHTS, request.getServletPath()); } User user = this.entityService.getByMrn(userMrn); if (user == null) { throw new McBasicRestException(HttpStatus.NOT_FOUND, MCIdRegConstants.USER_NOT_FOUND, request.getServletPath()); } if (user.getIdOrganization().compareTo(org.getId()) == 0) { this.entityService.delete(user.getId()); // Remove user from keycloak if created there. if (org.getIdentityProviderAttributes() == null || org.getIdentityProviderAttributes().isEmpty()) { keycloakAU.init(KeycloakAdminUtil.USER_INSTANCE); keycloakAU.deleteUser(user.getEmail()); } return new ResponseEntity<>(HttpStatus.OK); } throw new McBasicRestException(HttpStatus.FORBIDDEN, MCIdRegConstants.MISSING_RIGHTS, request.getServletPath()); } else { throw new McBasicRestException(HttpStatus.NOT_FOUND, MCIdRegConstants.ORG_NOT_FOUND, request.getServletPath()); } } /** * Returns a list of users belonging to the organization identified by the given ID * * @return a reply... * @throws McBasicRestException */ @RequestMapping(value = "/api/org/{orgMrn}/users", method = RequestMethod.GET, produces = "application/json;charset=UTF-8") @PreAuthorize("@accessControlUtil.hasAccessToOrg(#orgMrn)") public Page<User> getOrganizationUsers(HttpServletRequest request, @PathVariable String orgMrn, Pageable pageable) throws McBasicRestException { return this.getOrganizationEntities(request, orgMrn, pageable); } /** * Returns new certificate for the user identified by the given ID * * @return a reply... * @throws McBasicRestException */ @RequestMapping(value = "/api/org/{orgMrn}/user/{userMrn}/certificate/issue-new", method = RequestMethod.GET, produces = "application/json;charset=UTF-8") @PreAuthorize("hasRole('USER_ADMIN') and @accessControlUtil.hasAccessToOrg(#orgMrn)") public ResponseEntity<PemCertificate> newUserCert(HttpServletRequest request, @PathVariable String orgMrn, @PathVariable String userMrn) throws McBasicRestException { return this.newEntityCert(request, orgMrn, userMrn, "user"); } /** * Revokes certificate for the user identified by the given ID * * @return a reply... * @throws McBasicRestException */ @RequestMapping(value = "/api/org/{orgMrn}/user/{userMrn}/certificate/{certId}/revoke", method = RequestMethod.POST, produces = "application/json;charset=UTF-8") @PreAuthorize("hasRole('USER_ADMIN') and @accessControlUtil.hasAccessToOrg(#orgMrn)") public ResponseEntity<?> revokeUserCert(HttpServletRequest request, @PathVariable String orgMrn, @PathVariable String userMrn, @PathVariable Long certId, @Valid @RequestBody CertificateRevocation input) throws McBasicRestException { return this.revokeEntityCert(request, orgMrn, userMrn, certId, input); } /** * Sync user from keycloak, diff from create/update user is that this should only be done by * the keycloak sync-mechanism. * * @return a reply... * @throws McBasicRestException */ @ApiOperation(hidden = true, value = "Sync user from keycloak") @RequestMapping(value = "/api/org/{orgMrn}/user-sync/", method = RequestMethod.POST, produces = "application/json;charset=UTF-8") @ResponseBody public ResponseEntity<?> syncUser(HttpServletRequest request, @PathVariable String orgMrn, @RequestBody User input, @RequestParam(value = "org-name", required = false) String orgName, @RequestParam(value = "org-address", required = false) String orgAddress) throws McBasicRestException { if (!AccessControlUtil.isUserSync(this.userSyncMRN, this.userSyncO, this.userSyncOU, this.userSyncC)) { throw new McBasicRestException(HttpStatus.FORBIDDEN, MCIdRegConstants.MISSING_RIGHTS, request.getServletPath()); } Organization org = this.organizationService.getOrganizationByMrnNoFilter(orgMrn); // The organization does not exists - check if this a an organization hosted by an external "validator". if (org == null && orgAddress != null && orgName != null) { // Check that the org shortname is the same for the orgMrn and originalErrorMessage String orgShortname = MrnUtil.getOrgShortNameFromOrgMrn(orgMrn); if (!orgShortname.equals(MrnUtil.getOrgShortNameFromEntityMrn(input.getMrn()))) { throw new McBasicRestException(HttpStatus.BAD_REQUEST, MCIdRegConstants.URL_DATA_MISMATCH, request.getServletPath()); } // Since the permissions of this user will be used as a template for administrator permissions, it must be // verified that the user actually has some permissions. if (input.getPermissions() == null || input.getPermissions().isEmpty()) { throw new McBasicRestException(HttpStatus.BAD_REQUEST, MCIdRegConstants.ROLE_NOT_FOUND, request.getServletPath()); } // Check validators? String orgValidator = MrnUtil.getOrgValidatorFromOrgShortname(orgShortname); // Create the new org based on given info org = new Organization(); org.setName(orgName); org.setMrn(orgMrn); org.setApproved(true); org.setEmail(input.getEmail()); // Extract domain-name from the user email and use that for org url. int at = input.getEmail().indexOf("@"); String url = "http://" + input.getEmail().substring(at + 1); org.setUrl(url); // Extract country from address String country; String address; int lastComma = orgAddress.lastIndexOf(","); if (lastComma > 0) { country = orgAddress.substring(lastComma + 1).trim(); address = orgAddress.substring(0, lastComma).trim(); } else { country = "The Seven Seas"; address = orgAddress; } org.setAddress(address); org.setCountry(country); org.setFederationType("external-idp"); // save the new organization org = this.organizationService.save(org); // Create the initial roles for the organization. The permissions of the first user is used to define the ORG_ADMIN // Come on! That's a great idea!! if (input.getPermissions() != null) { for (String permission : input.getPermissions().split(",")) { Role newRole = new Role(); newRole.setRoleName("ROLE_ORG_ADMIN"); newRole.setPermission(permission.trim()); newRole.setIdOrganization(org.getId()); this.roleService.save(newRole); } } } if (org != null) { String userMrn = input.getMrn(); if (userMrn == null || userMrn.isEmpty()) { throw new McBasicRestException(HttpStatus.BAD_REQUEST, MCIdRegConstants.USER_NOT_FOUND, request.getServletPath()); } User oldUser = this.entityService.getByMrn(userMrn); // If user does not exists, we create him if (oldUser == null) { input.setIdOrganization(org.getId()); this.entityService.save(input); } else { // Update the existing user and save oldUser = input.selectiveCopyTo(oldUser); this.entityService.save(oldUser); } return new ResponseEntity<>(HttpStatus.OK); } else { throw new McBasicRestException(HttpStatus.NOT_FOUND, MCIdRegConstants.ORG_NOT_FOUND, request.getServletPath()); } } protected String getName(CertificateModel certOwner) { return ((User) certOwner).getFirstName() + " " + ((User) certOwner).getLastName(); } protected String getEmail(CertificateModel certOwner) { return ((User) certOwner).getEmail(); } @Override protected User getCertEntity(Certificate cert) { return cert.getUser(); } }