DomainUserManagementController.java

/*
 * Copyright 2019 the original author or authors.
 *
 * 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.bremersee.dccon.controller;

import static org.bremersee.security.core.AuthorityConstants.ADMIN_ROLE_NAME;

import java.util.List;
import java.util.Optional;
import javax.validation.Valid;
import org.bremersee.common.model.TwoLetterLanguageCode;
import org.bremersee.dccon.api.DomainUserManagementApi;
import org.bremersee.dccon.model.AvatarDefault;
import org.bremersee.dccon.model.DomainUser;
import org.bremersee.dccon.model.Password;
import org.bremersee.dccon.service.AuthenticationService;
import org.bremersee.dccon.service.DomainGroupService;
import org.bremersee.dccon.service.DomainUserService;
import org.bremersee.exception.ServiceException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RestController;

/**
 * The domain user management controller.
 *
 * @author Christian Bremer
 */
@RestController
public class DomainUserManagementController implements DomainUserManagementApi {

  private final DomainUserService domainUserService;

  private final DomainGroupService domainGroupService;

  private final AuthenticationService authenticationService;

  /**
   * Instantiates a new domain user management controller.
   *
   * @param domainUserService the domain user service
   * @param domainGroupService the domain group service
   * @param authenticationService the authentication service
   */
  public DomainUserManagementController(
      final DomainUserService domainUserService,
      final DomainGroupService domainGroupService,
      final AuthenticationService authenticationService) {
    this.domainUserService = domainUserService;
    this.domainGroupService = domainGroupService;
    this.authenticationService = authenticationService;
  }

  @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DC_CON_ADMIN', 'ROLE_LOCAL_USER')")
  @Override
  public ResponseEntity<List<DomainUser>> getUsers(final String sort, String query) {
    return ResponseEntity.ok(domainUserService.getUsers(sort, query));
  }

  @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DC_CON_ADMIN')")
  @Override
  public ResponseEntity<DomainUser> addUser(
      final Boolean email,
      final TwoLetterLanguageCode language,
      final DomainUser domainUser) {
    return ResponseEntity.ok(domainUserService.addUser(domainUser, email, language));
  }

  @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DC_CON_ADMIN', 'ROLE_LOCAL_USER')")
  @Override
  public ResponseEntity<DomainUser> getUser(
      final String userName) {
    return ResponseEntity.of(domainUserService.getUser(userName));
  }

  @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DC_CON_ADMIN', 'ROLE_LOCAL_USER')")
  @Override
  public ResponseEntity<byte[]> getUserAvatar(
      final String userName,
      final AvatarDefault avatarDefault,
      final Integer size) {

    return domainUserService.getUserAvatar(userName, avatarDefault, size)
        .map(avatar -> ResponseEntity
            .status(HttpStatus.OK)
            .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + userName + ".jpg\"")
            .body(avatar))
        .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND).build());
  }

  @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DC_CON_ADMIN')")
  @Override
  public ResponseEntity<DomainUser> updateUser(
      final String userName,
      final Boolean updateGroups,
      @Valid final DomainUser domainUser) {
    return ResponseEntity.of(domainUserService.updateUser(userName, updateGroups, domainUser));
  }

  @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DC_CON_ADMIN', 'ROLE_LOCAL_USER')")
  @Override
  public ResponseEntity<Void> updateUserPassword(
      final String userName,
      final Boolean email,
      final TwoLetterLanguageCode language,
      @Valid final Password newPassword) {

    final boolean sendEmail;
    if (!isAdmin()) {
      sendEmail = Boolean.FALSE;
      if (!isUser(userName)) {
        throw ServiceException.forbidden();
      } else if (!authenticationService.passwordMatches(userName, newPassword.getPreviousValue())) {
        throw ServiceException.badRequest(
            "Previous password does not match.",
            "password_does_not_match");
      }
    } else {
      sendEmail = Boolean.TRUE.equals(email);
    }
    domainUserService.updateUserPassword(userName, newPassword, sendEmail, language);
    return ResponseEntity.ok().build();
  }

  @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DC_CON_ADMIN', 'ROLE_LOCAL_USER')")
  @Override
  public ResponseEntity<Boolean> userExists(final String userName) {
    return ResponseEntity.ok(domainUserService.userExists(userName));
  }

  @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DC_CON_ADMIN', 'ROLE_LOCAL_USER')")
  @Override
  public ResponseEntity<Boolean> isUserNameInUse(String userName) {
    return ResponseEntity.ok(domainUserService.userExists(userName)
        || domainGroupService.groupExists(userName));
  }

  @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_DC_CON_ADMIN')")
  @Override
  public ResponseEntity<Boolean> deleteUser(final String userName) {
    return ResponseEntity.ok(domainUserService.deleteUser(userName));
  }

  private boolean isAdmin() {
    return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
        .map(Authentication::getAuthorities)
        .map(authorities -> authorities.stream()
            .map(GrantedAuthority::getAuthority)
            .anyMatch(roleName -> ADMIN_ROLE_NAME
                .equalsIgnoreCase(roleName) || "ROLE_DC_CON_ADMIN".equalsIgnoreCase(roleName)))
        .orElse(false);
  }

  private boolean isUser(String userName) {
    return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
        .map(Authentication::getName)
        .map(name -> name.equalsIgnoreCase(userName))
        .orElse(false);
  }

}