View Javadoc
1   /*
2    * Copyright (C) 2022 B3Partners B.V.
3    *
4    * SPDX-License-Identifier: MIT
5    */
6   package org.tailormap.api.security;
7   
8   import java.util.HashSet;
9   import java.util.List;
10  import java.util.Optional;
11  import java.util.Set;
12  import org.springframework.security.authentication.AnonymousAuthenticationToken;
13  import org.springframework.security.core.Authentication;
14  import org.springframework.security.core.GrantedAuthority;
15  import org.springframework.security.core.context.SecurityContextHolder;
16  import org.springframework.stereotype.Service;
17  import org.tailormap.api.persistence.Application;
18  import org.tailormap.api.persistence.GeoService;
19  import org.tailormap.api.persistence.Group;
20  import org.tailormap.api.persistence.json.AuthorizationRule;
21  import org.tailormap.api.persistence.json.AuthorizationRuleDecision;
22  import org.tailormap.api.persistence.json.GeoServiceLayer;
23  import org.tailormap.api.persistence.json.GeoServiceLayerSettings;
24  
25  /**
26   * Validates access control rules. Any call to mayUserRead will verify that the currently logged in
27   * user is not only allowed to read the current object, but any object above and below it in the
28   * hierarchy.
29   */
30  @Service
31  public class AuthorizationService {
32    public static final String ACCESS_TYPE_READ = "read";
33  
34    private Optional<AuthorizationRuleDecision> isAuthorizedByRules(
35        List<AuthorizationRule> rules, String type) {
36      Authentication auth = SecurityContextHolder.getContext().getAuthentication();
37      Set<String> groups;
38  
39      if (auth == null || auth instanceof AnonymousAuthenticationToken) {
40        groups = Set.of(Group.ANONYMOUS);
41      } else {
42        groups = new HashSet<>();
43        groups.add(Group.ANONYMOUS);
44        groups.add(Group.AUTHENTICATED);
45  
46        for (GrantedAuthority authority : auth.getAuthorities()) {
47          groups.add(authority.getAuthority());
48        }
49      }
50  
51      // Admins are allowed access to anything.
52      if (groups.contains(Group.ADMIN)) {
53        return Optional.of(AuthorizationRuleDecision.ALLOW);
54      }
55  
56      boolean hasValidRule = false;
57  
58      for (AuthorizationRule rule : rules) {
59        boolean matchesGroup = groups.contains(rule.getGroupName());
60        if (!matchesGroup) {
61          continue;
62        }
63  
64        hasValidRule = true;
65  
66        AuthorizationRuleDecision value = rule.getDecisions().get(type);
67        if (value == null) {
68          return Optional.empty();
69        }
70  
71        if (value.equals(AuthorizationRuleDecision.ALLOW)) {
72          return Optional.of(value);
73        }
74      }
75  
76      if (hasValidRule) {
77        return Optional.of(AuthorizationRuleDecision.DENY);
78      }
79  
80      return Optional.empty();
81    }
82  
83    /**
84     * Verifies that this user may read this Application.
85     *
86     * @param application the Application to check
87     * @return the results from the access control checks.
88     */
89    public boolean mayUserRead(Application application) {
90      return isAuthorizedByRules(application.getAuthorizationRules(), ACCESS_TYPE_READ)
91          .equals(Optional.of(AuthorizationRuleDecision.ALLOW));
92    }
93  
94    /**
95     * Verifies that this user may read this GeoService.
96     *
97     * @param geoService the GeoService to check
98     * @return the results from the access control checks.
99     */
100   public boolean mayUserRead(GeoService geoService) {
101     return isAuthorizedByRules(geoService.getAuthorizationRules(), ACCESS_TYPE_READ)
102         .equals(Optional.of(AuthorizationRuleDecision.ALLOW));
103   }
104 
105   /**
106    * Verifies that this user may read the Layer in context of the GeoService.
107    *
108    * @param geoService the GeoService to check
109    * @param layer the GeoServiceLayer to check
110    * @return the results from the access control checks.
111    */
112   public boolean mayUserRead(GeoService geoService, GeoServiceLayer layer) {
113     Optional<AuthorizationRuleDecision> geoserviceDecision =
114         isAuthorizedByRules(geoService.getAuthorizationRules(), ACCESS_TYPE_READ);
115 
116     if (geoserviceDecision.equals(Optional.of(AuthorizationRuleDecision.DENY))) {
117       return false;
118     }
119 
120     GeoServiceLayerSettings settings =
121         geoService.getSettings().getLayerSettings().get(layer.getName());
122     if (settings != null && settings.getAuthorizationRules() != null) {
123       Optional<AuthorizationRuleDecision> decision =
124           isAuthorizedByRules(settings.getAuthorizationRules(), ACCESS_TYPE_READ);
125       // If no authorization rules are present, fall back to GeoService authorization.
126       if (decision.isPresent() || !settings.getAuthorizationRules().isEmpty()) {
127         return decision.equals(Optional.of(AuthorizationRuleDecision.ALLOW));
128       }
129     }
130 
131     return geoserviceDecision.equals(Optional.of(AuthorizationRuleDecision.ALLOW));
132   }
133 
134   /**
135    * To avoid exposing a secured service by proxying it to everyone, do not proxy a secured geo
136    * service when the application is public (accessible by anonymous users). Do not even allow
137    * proxying a secured service if the user is logged viewing a public app!
138    *
139    * @param application The application
140    * @param geoService The geo service
141    * @return Whether to deny proxying this service for the application
142    */
143   public boolean mustDenyAccessForSecuredProxy(Application application, GeoService geoService) {
144     if (!Boolean.TRUE.equals(geoService.getSettings().getUseProxy())) {
145       return false;
146     }
147     if (geoService.getAuthentication() == null) {
148       return false;
149     }
150     return application.getAuthorizationRules().stream()
151         .anyMatch(
152             rule ->
153                 Group.ANONYMOUS.equals(rule.getGroupName())
154                     && AuthorizationRuleDecision.ALLOW.equals(
155                         rule.getDecisions().get(ACCESS_TYPE_READ)));
156   }
157 }