Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 9223bc22

Přidáno uživatelem Jakub Danek před více než 5 roky(ů)

re #37 protect getUser and getUsers processes on server-side

Related refactoring - moving relevant logic to UserManager interface
and to /users API endpoint.

Zobrazit rozdíly:

server/src/main/java/org/danekja/ymanager/business/ApiManager.java
1 1
package org.danekja.ymanager.business;
2 2

  
3 3
import org.danekja.ymanager.domain.*;
4
import org.danekja.ymanager.dto.*;
5 4
import org.danekja.ymanager.dto.DefaultSettings;
5
import org.danekja.ymanager.dto.*;
6 6
import org.danekja.ymanager.repository.RequestRepository;
7 7
import org.danekja.ymanager.repository.UserRepository;
8 8
import org.danekja.ymanager.repository.VacationRepository;
9
import org.danekja.ymanager.ws.rest.RESTFullException;
9
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
10 10
import org.slf4j.Logger;
11 11
import org.slf4j.LoggerFactory;
12 12
import org.springframework.beans.factory.annotation.Autowired;
......
25 25
     */
26 26
    private static final Logger log = LoggerFactory.getLogger(UserRepository.class);
27 27

  
28
    private static final int WEEK_LENGTH = 7;
29

  
30 28
    private RequestRepository requestRepository;
31 29
    private UserRepository userRepository;
32 30
    private VacationRepository vacationRepository;
......
38 36
        this.vacationRepository = vacationRepository;
39 37
    }
40 38

  
41
    @Override
42
    public List<BasicProfileUser> getUsers(Status status) throws RESTFullException {
43
        try {
44
            List<BasicProfileUser> users = userRepository.getAllBasicUsers(status == null ? Status.ACCEPTED : status);
45

  
46
            LocalDate today = LocalDate.now();
47
            LocalDate weekBefore = today.minusDays(ApiManager.WEEK_LENGTH);
48
            LocalDate weekAfter = today.plusDays(ApiManager.WEEK_LENGTH);
49
            for (BasicProfileUser user : users) {
50
                user.setCalendar(vacationRepository.getVacationDays(user.getId(), weekBefore, weekAfter));
51
            }
52

  
53
            return users;
54

  
55
        } catch (DataAccessException e) {
56
            log.error(e.getMessage());
57
            throw new RESTFullException(e.getMessage(), "database.error");
58
        }
59
    }
60

  
61 39
    @Override
62 40
    public List<VacationRequest> getVacationRequests(Status status) throws RESTFullException {
63 41
        try {
......
78 56
        }
79 57
    }
80 58

  
81
    @Override
82
    public FullUserProfile getUserProfile(Long userId) throws RESTFullException {
83
        try {
84
            return userRepository.getFullUser(userId);
85
        } catch (DataAccessException e) {
86
            log.error(e.getMessage());
87
            throw new RESTFullException(e.getMessage(), "database.error");
88
        }
89
    }
90

  
91 59
    @Override
92 60
    public DefaultSettings getDefaultSettings() throws RESTFullException {
93 61
        try {
server/src/main/java/org/danekja/ymanager/business/FileService.java
1 1
package org.danekja.ymanager.business;
2 2

  
3
import org.danekja.ymanager.ws.rest.RESTFullException;
3
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
4 4

  
5 5
public interface FileService {
6 6

  
server/src/main/java/org/danekja/ymanager/business/FileServiceImpl.java
1 1
package org.danekja.ymanager.business;
2 2

  
3
import org.danekja.ymanager.ws.rest.RESTFullException;
4 3
import org.apache.pdfbox.pdmodel.PDDocument;
5 4
import org.apache.pdfbox.pdmodel.PDPage;
6 5
import org.apache.pdfbox.pdmodel.PDPageContentStream;
7 6
import org.apache.pdfbox.pdmodel.common.PDRectangle;
8 7
import org.apache.pdfbox.pdmodel.font.PDType1Font;
9 8
import org.danekja.ymanager.business.file.excel.*;
9
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
10 10
import org.springframework.stereotype.Component;
11 11

  
12 12
import java.io.ByteArrayInputStream;
server/src/main/java/org/danekja/ymanager/business/Manager.java
3 3
import org.danekja.ymanager.domain.RequestType;
4 4
import org.danekja.ymanager.domain.Status;
5 5
import org.danekja.ymanager.dto.*;
6
import org.danekja.ymanager.ws.rest.RESTFullException;
6
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
7 7

  
8 8
import java.time.LocalDate;
9 9
import java.util.List;
10 10

  
11 11
public interface Manager {
12 12

  
13
    List<BasicProfileUser> getUsers(Status status) throws RESTFullException;
14 13

  
15 14
    List<VacationRequest> getVacationRequests(Status status) throws RESTFullException;
16 15

  
17 16
    List<AuthorizationRequest> getAuthorizationRequests(Status status) throws RESTFullException;
18 17

  
19
    FullUserProfile getUserProfile(Long userId) throws RESTFullException;
20

  
21 18
    DefaultSettings getDefaultSettings() throws RESTFullException;
22 19

  
23 20
    List<VacationDay> getUserCalendar(Long userId, LocalDate fromDate, LocalDate toDate, Status status) throws RESTFullException;
server/src/main/java/org/danekja/ymanager/business/UserManager.java
1 1
package org.danekja.ymanager.business;
2 2

  
3
import org.danekja.ymanager.domain.Status;
3 4
import org.danekja.ymanager.domain.User;
5
import org.danekja.ymanager.dto.BasicProfileUser;
6
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
7

  
8
import java.util.List;
4 9

  
5 10
/**
6 11
 * Interface for application logic handler of User entities.
......
8 13
public interface UserManager {
9 14

  
10 15
    /**
11
     * Gets user by email (username)
16
     * List all users with given status.
17
     * <p>
18
     * TODO: refactor to return domain class instead of DTO as part of #32
19
     *
20
     * @param status status filter value
21
     * @return list of users or empty list if none found
22
     * @throws RESTFullException
23
     */
24
    List<BasicProfileUser> getUsers(Status status) throws RESTFullException;
25

  
26
    /**
27
     * Gets user by id (PK)
12 28
     *
13
     * @param email email value, used as search key
29
     * @param id id value, used as search key
14 30
     * @return found user Object or null
15 31
     */
16
    User getUser(String email);
32
    User getUser(Long id);
17 33
}
server/src/main/java/org/danekja/ymanager/business/auth/AuthExpressions.java
1
package org.danekja.ymanager.business.auth;
2

  
3
/**
4
 * This class holds String expressions used in authorization checks.
5
 * <p>
6
 * Please comment each expressions well enough to clarify what constraints on user authority it enforces.
7
 */
8
public class AuthExpressions {
9

  
10
    /**
11
     * Used in cases where the action can be triggered by either:
12
     * <ul>
13
     * <li>employer - typically can edit all records</li>
14
     * <li>data owner - employee can edit only his records</li>
15
     * </ul>
16
     * <p>
17
     * In this case, the protected method needs to take <b>id</b> parameter which represents the "userId" value and
18
     * is compared to principal id.
19
     */
20
    public static final String SELF_ONLY_ID_PARAM = "hasAuthority('EMPLOYER') or #id == authentication.principal.id";
21
}
server/src/main/java/org/danekja/ymanager/business/auth/anot/IsEmployer.java
1
package org.danekja.ymanager.business.auth.anot;
2

  
3

  
4
import org.springframework.security.access.prepost.PreAuthorize;
5

  
6
import java.lang.annotation.*;
7

  
8
/**
9
 * This annotation enforces EMPLOYER user role.
10
 */
11
@Target({ElementType.METHOD, ElementType.TYPE})
12
@Retention(RetentionPolicy.RUNTIME)
13
@Inherited
14
@Documented
15
@PreAuthorize("hasAuthority('EMPLOYER')")
16
public @interface IsEmployer {
17
}
server/src/main/java/org/danekja/ymanager/business/impl/DefaultUserManager.java
1 1
package org.danekja.ymanager.business.impl;
2 2

  
3 3
import org.danekja.ymanager.business.UserManager;
4
import org.danekja.ymanager.business.auth.AuthExpressions;
5
import org.danekja.ymanager.business.auth.anot.IsEmployer;
6
import org.danekja.ymanager.domain.Status;
4 7
import org.danekja.ymanager.domain.User;
8
import org.danekja.ymanager.dto.BasicProfileUser;
5 9
import org.danekja.ymanager.repository.UserRepository;
10
import org.danekja.ymanager.repository.VacationRepository;
11
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
12
import org.slf4j.Logger;
13
import org.slf4j.LoggerFactory;
6 14
import org.springframework.beans.factory.annotation.Autowired;
15
import org.springframework.dao.DataAccessException;
16
import org.springframework.security.access.prepost.PreAuthorize;
7 17
import org.springframework.security.core.userdetails.UserDetails;
8 18
import org.springframework.security.core.userdetails.UserDetailsService;
9 19
import org.springframework.security.core.userdetails.UsernameNotFoundException;
10 20
import org.springframework.stereotype.Service;
11 21

  
22
import java.time.LocalDate;
23
import java.util.List;
24

  
12 25
/**
13 26
 * Default implementation of User-related application logic.
14 27
 * <p>
......
17 30
@Service
18 31
public class DefaultUserManager implements UserManager, UserDetailsService {
19 32

  
33
    private static final Logger LOG = LoggerFactory.getLogger(DefaultUserManager.class);
34

  
20 35
    private final UserRepository userRepository;
36
    private final VacationRepository vacationRepository;
21 37

  
22 38
    @Autowired
23
    public DefaultUserManager(UserRepository userRepository) {
39
    public DefaultUserManager(UserRepository userRepository, VacationRepository vacationRepository) {
24 40
        this.userRepository = userRepository;
41
        this.vacationRepository = vacationRepository;
42
    }
43

  
44
    @Override
45
    @PreAuthorize(AuthExpressions.SELF_ONLY_ID_PARAM)
46
    public User getUser(Long id) {
47
        return userRepository.getUser(id);
25 48
    }
26 49

  
27 50
    @Override
28
    public User getUser(String email) {
29
        return userRepository.getUser(email);
51
    @IsEmployer
52
    public List<BasicProfileUser> getUsers(Status status) throws RESTFullException {
53
        try {
54
            List<BasicProfileUser> users = userRepository.getAllBasicUsers(status == null ? Status.ACCEPTED : status);
55

  
56
            LocalDate today = LocalDate.now();
57
            LocalDate weekBefore = today.minusWeeks(1);
58
            LocalDate weekAfter = today.plusWeeks(1);
59
            for (BasicProfileUser user : users) {
60
                user.setCalendar(vacationRepository.getVacationDays(user.getId(), weekBefore, weekAfter));
61
            }
62

  
63
            return users;
64

  
65
        } catch (DataAccessException e) {
66
            LOG.error(e.getMessage());
67
            throw new RESTFullException(e.getMessage(), "database.error");
68
        }
30 69
    }
31 70

  
32 71
    @Override
33 72
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
34
        UserDetails details = getUser(username);
73
        UserDetails details = userRepository.getUser(username);
35 74

  
36 75
        //interface contract enforces this behavior
37 76
        if (details == null) {
server/src/main/java/org/danekja/ymanager/ws/rest/ApiController.java
5 5
import org.danekja.ymanager.business.Manager;
6 6
import org.danekja.ymanager.domain.RequestType;
7 7
import org.danekja.ymanager.domain.Status;
8
import org.danekja.ymanager.dto.*;
8
import org.danekja.ymanager.dto.BasicRequest;
9
import org.danekja.ymanager.dto.DefaultSettings;
10
import org.danekja.ymanager.dto.UserSettings;
11
import org.danekja.ymanager.dto.VacationDay;
9 12
import org.danekja.ymanager.util.localization.Language;
10 13
import org.springframework.beans.factory.annotation.Autowired;
11 14
import org.springframework.http.HttpHeaders;
......
48 51

  
49 52
    // *********************** GET ****************************
50 53

  
51
    @RequestMapping(value = "/users", method=GET)
52
    public ResponseEntity users(
53
            @RequestParam(value = "lang", required = false) String lang,
54
            @RequestParam(value = "status", required = false) String status)
55
    {
56
        return handle(Language.getLanguage(lang), () ->
57
                manager.getUsers(Status.getStatus(status))
58
        );
59
    }
60

  
61 54
    @RequestMapping(value = "/users/requests/vacation", method=GET)
62 55
    public ResponseEntity usersRequestsVacation(
63 56
            @RequestParam(value = "lang", required = false) String lang,
server/src/main/java/org/danekja/ymanager/ws/rest/BaseController.java
2 2

  
3 3
import org.danekja.ymanager.util.localization.Language;
4 4
import org.danekja.ymanager.util.localization.Message;
5
import org.danekja.ymanager.ws.rest.exceptions.RESTFullException;
5 6
import org.slf4j.Logger;
6 7
import org.slf4j.LoggerFactory;
7 8
import org.springframework.http.MediaType;
server/src/main/java/org/danekja/ymanager/ws/rest/RESTFullException.java
1
package org.danekja.ymanager.ws.rest;
2

  
3
public class RESTFullException extends Exception {
4

  
5
    private final String messageKey;
6

  
7
    public RESTFullException(String message, String messageKey) {
8
        super(message);
9
        this.messageKey = messageKey;
10
    }
11

  
12
    @Override
13
    public String getLocalizedMessage() {
14
        return messageKey;
15
    }
16
}
server/src/main/java/org/danekja/ymanager/ws/rest/UserController.java
1 1
package org.danekja.ymanager.ws.rest;
2 2

  
3
import org.danekja.ymanager.business.Manager;
3
import org.danekja.ymanager.business.UserManager;
4
import org.danekja.ymanager.domain.Status;
4 5
import org.danekja.ymanager.domain.User;
6
import org.danekja.ymanager.dto.FullUserProfile;
5 7
import org.danekja.ymanager.util.localization.Language;
8
import org.danekja.ymanager.ws.rest.exceptions.NotFoundException;
6 9
import org.springframework.beans.factory.annotation.Autowired;
7 10
import org.springframework.http.ResponseEntity;
8 11
import org.springframework.security.authentication.AnonymousAuthenticationToken;
......
16 19
@RequestMapping("/users")
17 20
public class UserController extends BaseController {
18 21

  
19
    private final Manager manager;
22
    private final UserManager userManager;
20 23

  
21 24
    @Autowired
22
    public UserController(Manager manager) {
23
        this.manager = manager;
25
    public UserController(UserManager userManager) {
26
        this.userManager = userManager;
24 27
    }
25 28

  
29
    @GetMapping
30
    public ResponseEntity users(
31
            @RequestParam(value = "lang", required = false) String lang,
32
            @RequestParam(value = "status", required = false) String status) {
33
        return handle(Language.getLanguage(lang), () ->
34
                userManager.getUsers(Status.getStatus(status))
35
        );
36
    }
37

  
38

  
26 39
    /**
27 40
     * Returns currently authenticated user.
28 41
     *
......
30 43
     * @return user information object
31 44
     */
32 45
    @GetMapping("/current/profile")
33
    public ResponseEntity getCurrentUser(Authentication auth) {
46
    public FullUserProfile getCurrentUser(Authentication auth) throws Exception {
34 47

  
35 48
        if (auth instanceof AnonymousAuthenticationToken
36 49
                || auth.getPrincipal() == null
......
38 51
            return null;
39 52
        }
40 53

  
41
        return getUserProfile(((User) auth.getPrincipal()).getId(), null);
54
        return getUserProfile(((User) auth.getPrincipal()).getId());
42 55
    }
43 56

  
44 57
    @GetMapping("/{id}/profile")
45
    public ResponseEntity getUserProfile(
46
            @PathVariable("id") Long id,
47
            @RequestParam(value = "lang", required = false) String lang) {
48
        return handle(Language.getLanguage(lang), () ->
49
                manager.getUserProfile(id)
50
        );
58
    public FullUserProfile getUserProfile(@PathVariable("id") Long id) throws Exception {
59
        User u = userManager.getUser(id);
60

  
61
        if (u == null) {
62
            throw new NotFoundException();
63
        }
64

  
65
        return new FullUserProfile(u);
51 66
    }
52 67
}
server/src/main/java/org/danekja/ymanager/ws/rest/exceptions/NotFoundException.java
1
package org.danekja.ymanager.ws.rest.exceptions;
2

  
3
public class NotFoundException extends Exception {
4
}
server/src/main/java/org/danekja/ymanager/ws/rest/exceptions/RESTFullException.java
1
package org.danekja.ymanager.ws.rest.exceptions;
2

  
3
public class RESTFullException extends Exception {
4

  
5
    private final String messageKey;
6

  
7
    public RESTFullException(String message, String messageKey) {
8
        super(message);
9
        this.messageKey = messageKey;
10
    }
11

  
12
    @Override
13
    public String getLocalizedMessage() {
14
        return messageKey;
15
    }
16
}
webapp/src/app/services/api/user.service.ts
3 3

  
4 4
import {Calendar, CalendarEdit, PostCalendar} from '../../models/calendar.model';
5 5
import {BasicService} from './basic.service';
6
import {catchError} from 'rxjs/operators';
6
import {catchError, flatMap} from 'rxjs/operators';
7 7
import {Languages, RequestStatus, RequestTypes} from '../../enums/common.enum';
8 8
import {NotificationSettings, UserSettings} from '../../models/settings.model';
9 9
import {UserRequest} from '../../models/requests.model';
10 10
import {MatSnackBar} from '@angular/material';
11 11
import {DateFormatterService} from '../util/date-formatter.service';
12 12
import {TranslateService} from '@ngx-translate/core';
13
import {ProfileService} from "../util/profile.service";
13 14

  
14 15
@Injectable({
15 16
  providedIn: 'root'
16 17
})
17 18
export class UserService extends BasicService { // dost podobny k usersService, mozna zmenit v rest api
18
  private _userUrl = this.baseUrl + '/api/users/';
19
  private _userUrl = this.baseUrl + '/api/user/';
19 20

  
20
  constructor(protected http: HttpClient, protected snackBar: MatSnackBar, protected translateService: TranslateService, private dateFormater: DateFormatterService) {
21
  constructor(protected http: HttpClient, protected snackBar: MatSnackBar, protected translateService: TranslateService, protected profileService: ProfileService, private dateFormater: DateFormatterService) {
21 22
    super(http, snackBar, translateService);
22 23
  }
23 24

  
......
27 28
   * @param from returns days from this date forward
28 29
   */
29 30
  getLoggedUserCalendar(from: Date) {
30
    return this.makeGetCalendarApiCall('me', from, null, null, null);
31
    return this.profileService.getLoggedUser(true).pipe(flatMap((profile) => this.makeGetCalendarApiCall(profile.id.toString(), from, null, null, null)));
31 32
  }
32 33

  
33 34
  /**
......
39 40
   * @param status filter by status
40 41
   */
41 42
  getLoggedUserCalendarWithOptions(from: Date, to: Date, language: Languages, status: RequestStatus) {
42
    return this.makeGetCalendarApiCall('me', from, to, language, status);
43
    return this.profileService.getLoggedUser(true).pipe(flatMap((profile) => this.makeGetCalendarApiCall(profile.id.toString(), from, to, language, status)));
43 44
  }
44 45

  
45 46
  /**
webapp/src/app/services/api/users.service.ts
201 201

  
202 202
  /**
203 203
   * Získání profilu aktuálně přihlášeného uživatele nebo uživatele podle zadaného id.
204
   * GET /user/<{id} || me>/profile?[lang=<CZ,EN>]
205
   * @param id id of profile to get (number or 'me')
204
   * GET /user/<{id} || current>/profile?[lang=<CZ,EN>]
205
   * @param id id of profile to get (number or 'current')
206 206
   * @param language filter by language
207 207
   */
208 208
  private makeGetProfileApiCall(id: string, language: string) {

Také k dispozici: Unified diff