Skip to content

Commit 57c84a7

Browse files
Khor Shu Hengkhorshuheng
authored andcommitted
Support Keto authorization directly
Signed-off-by: Khor Shu Heng <khor.heng@go-jek.com>
1 parent 2404065 commit 57c84a7

File tree

6 files changed

+560
-8
lines changed

6 files changed

+560
-8
lines changed

common/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,12 @@
125125
<groupId>com.google.auth</groupId>
126126
<artifactId>google-auth-library-oauth2-http</artifactId>
127127
</dependency>
128-
128+
<dependency>
129+
<groupId>sh.ory.keto</groupId>
130+
<artifactId>keto-client</artifactId>
131+
<version>0.5.7-alpha.1.pre.0</version>
132+
</dependency>
133+
129134
<!-- HTTP/REST -->
130135
<dependency>
131136
<groupId>org.openapitools</groupId>

common/src/main/java/feast/common/auth/config/SecurityConfig.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import feast.common.auth.authentication.DefaultJwtAuthenticationProvider;
2020
import feast.common.auth.authorization.AuthorizationProvider;
2121
import feast.common.auth.providers.http.HttpAuthorizationProvider;
22+
import feast.common.auth.providers.keto.KetoAuthorizationProvider;
2223
import java.util.ArrayList;
2324
import java.util.List;
2425
import java.util.Map;
@@ -107,12 +108,40 @@ AccessDecisionManager accessDecisionManager() {
107108
AuthorizationProvider authorizationProvider() {
108109
if (securityProperties.getAuthentication().isEnabled()
109110
&& securityProperties.getAuthorization().isEnabled()) {
111+
// Merge authentication and authorization options to create HttpAuthorizationProvider.
112+
Map<String, String> options = securityProperties.getAuthorization().getOptions();
113+
114+
options.putAll(securityProperties.getAuthentication().getOptions());
110115
switch (securityProperties.getAuthorization().getProvider()) {
111116
case "http":
112-
// Merge authenticatoin and authorization options to create HttpAuthorizationProvider.
113-
Map<String, String> options = securityProperties.getAuthorization().getOptions();
114-
options.putAll(securityProperties.getAuthentication().getOptions());
115117
return new HttpAuthorizationProvider(options);
118+
case "keto":
119+
String subjectClaim =
120+
options.get(SecurityProperties.AuthenticationProperties.SUBJECT_CLAIM);
121+
String flavor = options.get("flavor");
122+
String action = options.get("action");
123+
String subjectPrefix = options.get("subjectPrefix");
124+
String resourcePrefix = options.get("resourcePrefix");
125+
126+
KetoAuthorizationProvider.Builder builder =
127+
new KetoAuthorizationProvider.Builder(options.get("authorizationUrl"));
128+
if (subjectClaim != null) {
129+
builder = builder.withSubjectClaim(subjectClaim);
130+
}
131+
if (flavor != null) {
132+
builder = builder.withFlavor(flavor);
133+
}
134+
if (action != null) {
135+
builder = builder.withAction(action);
136+
}
137+
if (subjectPrefix != null) {
138+
builder = builder.withSubjectPrefix(subjectPrefix);
139+
}
140+
if (resourcePrefix != null) {
141+
builder = builder.withResourcePrefix(resourcePrefix);
142+
}
143+
144+
return builder.build();
116145
default:
117146
throw new IllegalArgumentException(
118147
"Please configure an Authorization Provider if you have enabled authorization.");

common/src/main/java/feast/common/auth/config/SecurityProperties.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static class AuthorizationProperties {
5353
private boolean enabled;
5454

5555
// Named authorization provider to use.
56-
@OneOfStrings({"none", "http"})
56+
@OneOfStrings({"none", "http", "keto"})
5757
private String provider;
5858

5959
// K/V options to initialize the provider with
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright 2018-2021 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.common.auth.providers.keto;
18+
19+
import feast.common.auth.authorization.AuthorizationProvider;
20+
import feast.common.auth.authorization.AuthorizationResult;
21+
import feast.common.auth.utils.AuthUtils;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
24+
import org.springframework.security.core.Authentication;
25+
import sh.ory.keto.ApiClient;
26+
import sh.ory.keto.ApiException;
27+
import sh.ory.keto.Configuration;
28+
import sh.ory.keto.api.EnginesApi;
29+
import sh.ory.keto.model.OryAccessControlPolicyAllowedInput;
30+
31+
public class KetoAuthorizationProvider implements AuthorizationProvider {
32+
33+
/** Builder for KetoAuthorizationProvider */
34+
public static class Builder {
35+
private final String url;
36+
private String subjectClaim = "email";
37+
private String flavor = "glob";
38+
private String action = "edit";
39+
private String subjectPrefix = "";
40+
private String resourcePrefix = "";
41+
42+
/**
43+
* Initialized builder for Keto authorization provider.
44+
*
45+
* @param url Url string for Keto server.
46+
* @return Returns Builder
47+
*/
48+
public Builder(String url) {
49+
this.url = url;
50+
}
51+
52+
/**
53+
* Set subject claim for authentication
54+
*
55+
* @param subjectClaim Subject claim. Default: email.
56+
* @return Returns Builder
57+
*/
58+
public Builder withSubjectClaim(String subjectClaim) {
59+
this.subjectClaim = subjectClaim;
60+
return this;
61+
}
62+
63+
/**
64+
* Set flavor for Keto authorization. One of [exact, glob regex]
65+
*
66+
* @param flavor Keto authorization flavor. Default: glob.
67+
* @return Returns Builder
68+
*/
69+
public Builder withFlavor(String flavor) {
70+
this.flavor = flavor;
71+
return this;
72+
}
73+
74+
/**
75+
* Set action that corresponds to the permission to edit a Feast project resource.
76+
*
77+
* @param action Keto action. Default: edit.
78+
* @return Returns Builder
79+
*/
80+
public Builder withAction(String action) {
81+
this.action = action;
82+
return this;
83+
}
84+
85+
/**
86+
* If set, The subject will be prefixed before sending the request to Keto. Example:
87+
* users:someuser@email.com
88+
*
89+
* @param prefix Subject prefix. Default: Empty string.
90+
* @return Returns Builder
91+
*/
92+
public Builder withSubjectPrefix(String prefix) {
93+
this.subjectPrefix = prefix;
94+
return this;
95+
}
96+
97+
/**
98+
* If set, The resource will be prefixed before sending the request to Keto. Example:
99+
* projects:somefeastproject
100+
*
101+
* @param prefix Resource prefix. Default: Empty string.
102+
* @return Returns Builder
103+
*/
104+
public Builder withResourcePrefix(String prefix) {
105+
this.resourcePrefix = prefix;
106+
return this;
107+
}
108+
109+
/**
110+
* Build KetoAuthorizationProvider
111+
*
112+
* @return Returns KetoAuthorizationProvider
113+
*/
114+
public KetoAuthorizationProvider build() {
115+
return new KetoAuthorizationProvider(this);
116+
}
117+
}
118+
119+
private static final Logger log = LoggerFactory.getLogger(KetoAuthorizationProvider.class);
120+
121+
private final EnginesApi apiInstance;
122+
private final String subjectClaim;
123+
private final String flavor;
124+
private final String action;
125+
private final String subjectPrefix;
126+
private final String resourcePrefix;
127+
128+
private KetoAuthorizationProvider(Builder builder) {
129+
ApiClient defaultClient = Configuration.getDefaultApiClient();
130+
defaultClient.setBasePath(builder.url);
131+
apiInstance = new EnginesApi(defaultClient);
132+
subjectClaim = builder.subjectClaim;
133+
flavor = builder.flavor;
134+
action = builder.action;
135+
subjectPrefix = builder.subjectPrefix;
136+
resourcePrefix = builder.resourcePrefix;
137+
}
138+
139+
@Override
140+
public AuthorizationResult checkAccessToProject(String projectId, Authentication authentication) {
141+
String subject = AuthUtils.getSubjectFromAuth(authentication, subjectClaim);
142+
OryAccessControlPolicyAllowedInput body = new OryAccessControlPolicyAllowedInput();
143+
body.setAction(action);
144+
body.setSubject(String.format("%s%s", subjectPrefix, subject));
145+
body.setResource(String.format("%s%s", resourcePrefix, projectId));
146+
try {
147+
sh.ory.keto.model.AuthorizationResult authResult =
148+
apiInstance.doOryAccessControlPoliciesAllow(flavor, body);
149+
if (authResult == null) {
150+
throw new RuntimeException(
151+
String.format(
152+
"Empty response returned for access to project %s for subject %s",
153+
projectId, subject));
154+
}
155+
if (authResult.getAllowed()) {
156+
return AuthorizationResult.success();
157+
}
158+
} catch (ApiException e) {
159+
log.error("API exception has occurred during authorization: {}", e.getMessage(), e);
160+
}
161+
162+
return AuthorizationResult.failed(
163+
String.format("Access denied to project %s for subject %s", projectId, subject));
164+
}
165+
}

0 commit comments

Comments
 (0)