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