Skip to content

Commit 27d950c

Browse files
committed
Cache authorization.
1 parent 554324d commit 27d950c

6 files changed

Lines changed: 301 additions & 33 deletions

File tree

auth/pom.xml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
<artifactId>feast-common</artifactId>
2929
<version>${project.version}</version>
3030
</dependency>
31+
<dependency>
32+
<groupId>org.springframework</groupId>
33+
<artifactId>spring-context-support</artifactId>
34+
</dependency>
3135
<dependency>
3236
<groupId>net.devh</groupId>
3337
<artifactId>grpc-server-spring-boot-starter</artifactId>
@@ -91,6 +95,17 @@
9195
<artifactId>jsr305</artifactId>
9296
<version>3.0.2</version>
9397
</dependency>
98+
<dependency>
99+
<groupId>org.springframework</groupId>
100+
<artifactId>spring-test</artifactId>
101+
<scope>test</scope>
102+
</dependency>
103+
<dependency>
104+
<groupId>org.mockito</groupId>
105+
<artifactId>mockito-core</artifactId>
106+
<version>${mockito.version}</version>
107+
<scope>test</scope>
108+
</dependency>
94109
</dependencies>
95110
<build>
96111
<plugins>
@@ -131,6 +146,10 @@
131146
<excludePackageNames>feast.auth.generated.client.api</excludePackageNames>
132147
</configuration>
133148
</plugin>
149+
<plugin>
150+
<groupId>org.jacoco</groupId>
151+
<artifactId>jacoco-maven-plugin</artifactId>
152+
</plugin>
134153
</plugins>
135154
</build>
136155
</project>

auth/src/main/java/feast/auth/authorization/HttpAuthorizationProvider.java

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,17 @@
1616
*/
1717
package feast.auth.authorization;
1818

19+
import feast.auth.config.CacheConfiguration;
1920
import feast.auth.generated.client.api.DefaultApi;
2021
import feast.auth.generated.client.invoker.ApiClient;
2122
import feast.auth.generated.client.invoker.ApiException;
2223
import feast.auth.generated.client.model.CheckAccessRequest;
24+
import feast.auth.utils.AuthUtils;
2325
import java.util.Map;
24-
import org.hibernate.validator.internal.constraintvalidators.bv.EmailValidator;
2526
import org.slf4j.Logger;
2627
import org.slf4j.LoggerFactory;
28+
import org.springframework.cache.annotation.Cacheable;
2729
import org.springframework.security.core.Authentication;
28-
import org.springframework.security.oauth2.jwt.Jwt;
2930

3031
/**
3132
* HTTPAuthorizationProvider uses an external HTTP service for authorizing requests. Please see
@@ -41,7 +42,7 @@ public class HttpAuthorizationProvider implements AuthorizationProvider {
4142
* The default subject claim is the key within the Authentication object where the user's identity
4243
* can be found
4344
*/
44-
private final String DEFAULT_SUBJECT_CLAIM = "email";
45+
private final String subjectClaim;
4546

4647
/**
4748
* Initializes the HTTPAuthorizationProvider
@@ -58,26 +59,29 @@ public HttpAuthorizationProvider(Map<String, String> options) {
5859
ApiClient apiClient = new ApiClient();
5960
apiClient.setBasePath(options.get("authorizationUrl"));
6061
this.defaultApiClient = new DefaultApi(apiClient);
62+
subjectClaim = options.get("subjectClaim");
6163
}
6264

6365
/**
64-
* Validates whether a user has access to a project
66+
* Validates whether a user has access to a project. @Cacheable is using {@link
67+
* CacheConfiguration} settings to cache output of the method {@link AuthorizationResult} for a
68+
* specified duration set in cache settings.
6569
*
6670
* @param projectId Name of the Feast project
6771
* @param authentication Spring Security Authentication object
6872
* @return AuthorizationResult result of authorization query
6973
*/
74+
@Cacheable(value = CacheConfiguration.AUTHORIZATION_CACHE)
7075
public AuthorizationResult checkAccessToProject(String projectId, Authentication authentication) {
7176

7277
CheckAccessRequest checkAccessRequest = new CheckAccessRequest();
7378
Object context = getContext(authentication);
74-
String subject = getSubjectFromAuth(authentication, DEFAULT_SUBJECT_CLAIM);
79+
String subject = AuthUtils.getSubjectFromAuth(authentication, subjectClaim);
7580
String resource = "projects:" + projectId;
7681
checkAccessRequest.setAction("ALL");
7782
checkAccessRequest.setContext(context);
7883
checkAccessRequest.setResource(resource);
7984
checkAccessRequest.setSubject(subject);
80-
8185
try {
8286
Jwt credentials = ((Jwt) authentication.getCredentials());
8387
// Make authorization request to external service
@@ -114,31 +118,4 @@ private Object getContext(Authentication authentication) {
114118
// Not implemented yet, left empty
115119
return new Object();
116120
}
117-
118-
/**
119-
* Get user email from their authentication object.
120-
*
121-
* @param authentication Spring Security Authentication object, used to extract user details
122-
* @param subjectClaim Indicates the claim where the subject can be found
123-
* @return String user email
124-
*/
125-
private String getSubjectFromAuth(Authentication authentication, String subjectClaim) {
126-
Jwt principle = ((Jwt) authentication.getPrincipal());
127-
Map<String, Object> claims = principle.getClaims();
128-
String subjectValue = (String) claims.get(subjectClaim);
129-
130-
if (subjectValue.isEmpty()) {
131-
throw new IllegalStateException(
132-
String.format("JWT does not have a valid claim %s.", subjectClaim));
133-
}
134-
135-
if (subjectClaim.equals("email")) {
136-
boolean validEmail = (new EmailValidator()).isValid(subjectValue, null);
137-
if (!validEmail) {
138-
throw new IllegalStateException("JWT contains an invalid email address");
139-
}
140-
}
141-
142-
return subjectValue;
143-
}
144121
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright 2018-2020 The Feast Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package feast.auth.config;
18+
19+
import com.google.common.cache.CacheBuilder;
20+
import feast.auth.utils.AuthUtils;
21+
import java.lang.reflect.Method;
22+
import java.util.concurrent.TimeUnit;
23+
import lombok.Getter;
24+
import lombok.Setter;
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.cache.Cache;
27+
import org.springframework.cache.CacheManager;
28+
import org.springframework.cache.annotation.CachingConfigurer;
29+
import org.springframework.cache.annotation.EnableCaching;
30+
import org.springframework.cache.concurrent.ConcurrentMapCache;
31+
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
32+
import org.springframework.cache.interceptor.CacheErrorHandler;
33+
import org.springframework.cache.interceptor.CacheResolver;
34+
import org.springframework.cache.interceptor.KeyGenerator;
35+
import org.springframework.context.annotation.Bean;
36+
import org.springframework.context.annotation.Configuration;
37+
import org.springframework.security.core.Authentication;
38+
39+
/** CacheConfiguration class defines Cache settings for HttpAuthorizationProvider class. */
40+
@Configuration
41+
@EnableCaching
42+
@Setter
43+
@Getter
44+
public class CacheConfiguration implements CachingConfigurer {
45+
46+
private static final int CACHE_SIZE = 10000;
47+
48+
public static int TTL = 60;
49+
50+
public static final String AUTHORIZATION_CACHE = "authorization";
51+
52+
@Autowired SecurityProperties secutiryProps;
53+
54+
@Bean
55+
public CacheManager cacheManager() {
56+
ConcurrentMapCacheManager cacheManager =
57+
new ConcurrentMapCacheManager(AUTHORIZATION_CACHE) {
58+
59+
@Override
60+
protected Cache createConcurrentMapCache(final String name) {
61+
return new ConcurrentMapCache(
62+
name,
63+
CacheBuilder.newBuilder()
64+
.expireAfterWrite(TTL, TimeUnit.SECONDS)
65+
.maximumSize(CACHE_SIZE)
66+
.build()
67+
.asMap(),
68+
false);
69+
}
70+
};
71+
72+
return cacheManager;
73+
}
74+
75+
@Override
76+
public CacheResolver cacheResolver() {
77+
// TODO Auto-generated method stub
78+
return null;
79+
}
80+
81+
/*
82+
* KeyGenerator for HttpAuthorizationProvider.checkAccessToProject() Method.
83+
* Key format : checkAccessToProject-<projectId>-<subjectClaim>
84+
*/
85+
@Override
86+
public KeyGenerator keyGenerator() {
87+
return (Object target, Method method, Object... params) -> {
88+
String projectId = (String) params[0];
89+
Authentication authentication = (Authentication) params[1];
90+
String subject =
91+
AuthUtils.getSubjectFromAuth(
92+
authentication, secutiryProps.getAuthorization().getOptions().get("subjectClaim"));
93+
return String.format("%s-%s-%s", method.getName(), projectId, subject);
94+
};
95+
}
96+
97+
@Override
98+
public CacheErrorHandler errorHandler() {
99+
// TODO Auto-generated method stub
100+
return null;
101+
}
102+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright 2018-2020 The Feast Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package feast.auth.utils;
18+
19+
import java.util.Map;
20+
import org.hibernate.validator.internal.constraintvalidators.bv.EmailValidator;
21+
import org.springframework.security.core.Authentication;
22+
import org.springframework.security.oauth2.jwt.Jwt;
23+
24+
public class AuthUtils {
25+
26+
// Suppresses default constructor, ensuring non-instantiability.
27+
private AuthUtils() {}
28+
29+
/**
30+
* Get user email from their authentication object.
31+
*
32+
* @param authentication Spring Security Authentication object, used to extract user details
33+
* @param subjectClaim Indicates the claim where the subject can be found
34+
* @return String user email
35+
*/
36+
public static String getSubjectFromAuth(Authentication authentication, String subjectClaim) {
37+
Jwt principle = ((Jwt) authentication.getPrincipal());
38+
Map<String, Object> claims = principle.getClaims();
39+
String subjectValue = (String) claims.getOrDefault(subjectClaim, "");
40+
41+
if (subjectValue.isEmpty()) {
42+
throw new IllegalStateException(
43+
String.format("JWT does not have a valid claim %s.", subjectClaim));
44+
}
45+
46+
if (subjectClaim.equals("email")) {
47+
boolean validEmail = (new EmailValidator()).isValid(subjectValue, null);
48+
if (!validEmail) {
49+
throw new IllegalStateException("JWT contains an invalid email address");
50+
}
51+
}
52+
return subjectValue;
53+
}
54+
}

0 commit comments

Comments
 (0)