Skip to content

Commit 59bf780

Browse files
committed
Add authorization test and minor refactoring.
1 parent 2461ac1 commit 59bf780

11 files changed

Lines changed: 358 additions & 21 deletions

File tree

serving/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,12 @@
334334
<version>3.0.0</version>
335335
<scope>test</scope>
336336
</dependency>
337+
<dependency>
338+
<groupId>sh.ory.keto</groupId>
339+
<artifactId>keto-client</artifactId>
340+
<version>0.4.4-alpha.1</version>
341+
<scope>test</scope>
342+
</dependency>
337343
</dependencies>
338344

339345
<profiles>

serving/src/main/java/feast/serving/controller/ServingServiceGRpcController.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import io.opentracing.Span;
3939
import io.opentracing.Tracer;
4040
import java.util.List;
41+
import java.util.Set;
42+
import java.util.stream.Collectors;
4143
import net.devh.boot.grpc.server.service.GrpcService;
4244
import org.slf4j.Logger;
4345
import org.springframework.beans.factory.annotation.Autowired;
@@ -83,11 +85,15 @@ public void getOnlineFeatures(
8385
Span span = tracer.buildSpan("getOnlineFeatures").start();
8486
try (Scope scope = tracer.scopeManager().activate(span, false)) {
8587
// authorize for the project in request object.
86-
this.authorizationService.authorizeRequest(
87-
SecurityContextHolder.getContext(), request.getProject());
88-
// authorize for projects set in feature list, backward compatibility for
89-
// <=v0.5.X
90-
this.checkProjectAccess(request.getFeaturesList());
88+
if (request.getProject() != null && !request.getProject().isEmpty()) {
89+
// project set at root level overrides the project set at feature set level
90+
this.authorizationService.authorizeRequest(
91+
SecurityContextHolder.getContext(), request.getProject());
92+
} else {
93+
// authorize for projects set in feature list, backward compatibility for
94+
// <=v0.5.X
95+
this.checkProjectAccess(request.getFeaturesList());
96+
}
9197
RequestHelper.validateOnlineRequest(request);
9298
GetOnlineFeaturesResponse onlineFeatures = servingService.getOnlineFeatures(request);
9399
responseObserver.onNext(onlineFeatures);
@@ -149,11 +155,17 @@ public void getJob(GetJobRequest request, StreamObserver<GetJobResponse> respons
149155
}
150156

151157
private void checkProjectAccess(List<FeatureReference> featureList) {
152-
featureList.stream()
153-
.forEach(
154-
featureRef -> {
155-
this.authorizationService.authorizeRequest(
156-
SecurityContextHolder.getContext(), featureRef.getProject());
157-
});
158+
Set<String> projectList =
159+
featureList.stream().map(FeatureReference::getProject).collect(Collectors.toSet());
160+
if (projectList.isEmpty()) {
161+
authorizationService.authorizeRequest(SecurityContextHolder.getContext(), "default");
162+
} else {
163+
projectList.stream()
164+
.forEach(
165+
project -> {
166+
this.authorizationService.authorizeRequest(
167+
SecurityContextHolder.getContext(), project);
168+
});
169+
}
158170
}
159171
}

serving/src/main/resources/application.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ feast:
3030
jwkEndpointURI: "https://www.googleapis.com/oauth2/v3/certs"
3131
authorization:
3232
enabled: false
33-
provider: none
33+
provider: http
3434
options:
3535
basePath: http://localhost:3000
3636

serving/src/test/java/feast/serving/it/AuthTestUtils.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import io.grpc.ManagedChannelBuilder;
4141
import java.io.IOException;
4242
import java.util.ArrayList;
43+
import java.util.Arrays;
44+
import java.util.Collections;
4345
import java.util.List;
4446
import java.util.Map;
4547
import java.util.concurrent.TimeUnit;
@@ -51,9 +53,17 @@
5153
import okhttp3.Response;
5254
import org.apache.commons.lang3.tuple.Pair;
5355
import org.junit.runners.model.InitializationError;
56+
import sh.ory.keto.ApiClient;
57+
import sh.ory.keto.ApiException;
58+
import sh.ory.keto.Configuration;
59+
import sh.ory.keto.api.EnginesApi;
60+
import sh.ory.keto.model.OryAccessControlPolicy;
61+
import sh.ory.keto.model.OryAccessControlPolicyRole;
5462

5563
public class AuthTestUtils {
5664

65+
private static final String DEFAULT_FLAVOR = "glob";
66+
5767
static SourceProto.Source defaultSource =
5868
createSource("kafka:9092,localhost:9094", "feast-features");
5969

@@ -208,4 +218,66 @@ public static void seedHydra(
208218
throw new InitializationError(response.message());
209219
}
210220
}
221+
222+
public static void seedKeto(String url, String project, String subjectInProject, String admin)
223+
throws ApiException {
224+
ApiClient ketoClient = Configuration.getDefaultApiClient();
225+
ketoClient.setBasePath(url);
226+
EnginesApi enginesApi = new EnginesApi(ketoClient);
227+
228+
// Add policies
229+
OryAccessControlPolicy adminPolicy = getAdminPolicy();
230+
enginesApi.upsertOryAccessControlPolicy(DEFAULT_FLAVOR, adminPolicy);
231+
232+
OryAccessControlPolicy projectPolicy = getMyProjectMemberPolicy(project);
233+
enginesApi.upsertOryAccessControlPolicy(DEFAULT_FLAVOR, projectPolicy);
234+
235+
// Add policy roles
236+
OryAccessControlPolicyRole adminPolicyRole = getAdminPolicyRole(admin);
237+
enginesApi.upsertOryAccessControlPolicyRole(DEFAULT_FLAVOR, adminPolicyRole);
238+
239+
OryAccessControlPolicyRole myProjectMemberPolicyRole =
240+
getMyProjectMemberPolicyRole(project, subjectInProject);
241+
enginesApi.upsertOryAccessControlPolicyRole(DEFAULT_FLAVOR, myProjectMemberPolicyRole);
242+
}
243+
244+
private static OryAccessControlPolicyRole getMyProjectMemberPolicyRole(
245+
String project, String subjectInProject) {
246+
OryAccessControlPolicyRole role = new OryAccessControlPolicyRole();
247+
role.setId(String.format("roles:%s-project-members", project));
248+
role.setMembers(Collections.singletonList("users:" + subjectInProject));
249+
return role;
250+
}
251+
252+
private static OryAccessControlPolicyRole getAdminPolicyRole(String subjectIsAdmin) {
253+
OryAccessControlPolicyRole role = new OryAccessControlPolicyRole();
254+
role.setId("roles:admin");
255+
role.setMembers(Collections.singletonList("users:" + subjectIsAdmin));
256+
return role;
257+
}
258+
259+
private static OryAccessControlPolicy getAdminPolicy() {
260+
OryAccessControlPolicy policy = new OryAccessControlPolicy();
261+
policy.setId("policies:admin");
262+
policy.subjects(Collections.singletonList("roles:admin"));
263+
policy.resources(Collections.singletonList("resources:**"));
264+
policy.actions(Collections.singletonList("actions:**"));
265+
policy.effect("allow");
266+
policy.conditions(null);
267+
return policy;
268+
}
269+
270+
private static OryAccessControlPolicy getMyProjectMemberPolicy(String project) {
271+
OryAccessControlPolicy policy = new OryAccessControlPolicy();
272+
policy.setId(String.format("policies:%s-project-members-policy", project));
273+
policy.subjects(Collections.singletonList(String.format("roles:%s-project-members", project)));
274+
policy.resources(
275+
Arrays.asList(
276+
String.format("resources:projects:%s", project),
277+
String.format("resources:projects:%s:**", project)));
278+
policy.actions(Collections.singletonList("actions:**"));
279+
policy.effect("allow");
280+
policy.conditions(null);
281+
return policy;
282+
}
211283
}

serving/src/test/java/feast/serving/it/BaseAuthIT.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
import java.net.InetAddress;
2020
import java.net.UnknownHostException;
21-
import java.util.HashMap;
22-
import java.util.Map;
2321
import org.springframework.boot.test.context.SpringBootTest;
2422
import org.springframework.test.context.ActiveProfiles;
2523
import org.springframework.test.context.DynamicPropertyRegistry;
@@ -45,7 +43,6 @@ public class BaseAuthIT {
4543
static final String CORE = "core_1";
4644

4745
static final String HYDRA = "hydra_1";
48-
static final Map<String, String> options = new HashMap<>();
4946
static final int HYDRA_PORT = 4445;
5047

5148
static CoreSimpleAPIClient insecureApiClient;
@@ -56,7 +53,7 @@ public class BaseAuthIT {
5653
static final int FEAST_SERVING_PORT = 6566;
5754

5855
@DynamicPropertySource
59-
static void initialize(DynamicPropertyRegistry registry) throws UnknownHostException {
56+
static void properties(DynamicPropertyRegistry registry) {
6057
registry.add("feast.stores[0].name", () -> "online");
6158
registry.add("feast.stores[0].type", () -> "REDIS");
6259
// Redis needs to accessible by both core and serving, hence using host address
@@ -66,7 +63,6 @@ static void initialize(DynamicPropertyRegistry registry) throws UnknownHostExcep
6663
try {
6764
return InetAddress.getLocalHost().getHostAddress();
6865
} catch (UnknownHostException e) {
69-
// TODO Auto-generated catch block
7066
e.printStackTrace();
7167
return "";
7268
}

serving/src/test/java/feast/serving/it/ServingServiceOauthAuthenticationIT.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.io.File;
3030
import java.io.IOException;
3131
import java.time.Duration;
32+
import java.util.HashMap;
3233
import java.util.Map;
3334
import org.junit.ClassRule;
3435
import org.junit.jupiter.api.BeforeAll;
@@ -52,6 +53,8 @@
5253
@Testcontainers
5354
public class ServingServiceOauthAuthenticationIT extends BaseAuthIT {
5455

56+
static final Map<String, String> options = new HashMap<>();
57+
5558
@ClassRule @Container
5659
public static DockerComposeContainer environment =
5760
new DockerComposeContainer(

0 commit comments

Comments
 (0)