Skip to content

Commit c97eee0

Browse files
authored
Implement authenticated read access (#2071)
* Implement authenticated read access * Implement tests for authenticated read access
1 parent b9faf0c commit c97eee0

File tree

6 files changed

+147
-3
lines changed

6 files changed

+147
-3
lines changed

app/src/main/java/io/apicurio/registry/auth/AuthConfig.java

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ public class AuthConfig {
4747
@ConfigProperty(name = "registry.auth.anonymous-read-access.enabled", defaultValue = "false")
4848
boolean anonymousReadAccessEnabled;
4949

50+
@ConfigProperty(name = "registry.auth.authenticated-read-access.enabled", defaultValue = "false")
51+
boolean authenticatedReadAccessEnabled;
52+
5053
@ConfigProperty(name = "registry.auth.roles.readonly", defaultValue = "sr-readonly")
5154
String readOnlyRole;
5255

@@ -85,6 +88,7 @@ void onConstruct() {
8588
log.debug("===============================");
8689
log.debug("Auth Enabled: " + authenticationEnabled);
8790
log.debug("Anonymous Read Access Enabled: " + anonymousReadAccessEnabled);
91+
log.debug("Authenticated Read Access Enabled: " + authenticatedReadAccessEnabled);
8892
log.debug("RBAC Enabled: " + roleBasedAuthorizationEnabled);
8993
if (roleBasedAuthorizationEnabled) {
9094
log.debug(" RBAC Roles: " + readOnlyRole + ", " + developerRole + ", " + adminRole);

app/src/main/java/io/apicurio/registry/auth/AuthorizedInterceptor.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ public Object authorizeMethod(InvocationContext context) throws Exception {
8888

8989
log.trace("Authentication enabled, protected resource: " + context.getMethod());
9090

91+
Authorized annotation = context.getMethod().getAnnotation(Authorized.class);
92+
9193
// If the securityIdentity is not set (or is anonymous)...
9294
if (securityIdentity == null || securityIdentity.isAnonymous()) {
93-
Authorized annotation = context.getMethod().getAnnotation(Authorized.class);
94-
9595
// Anonymous users are allowed to perform "None" operations.
9696
if (annotation.level() == AuthorizedLevel.None) {
9797
log.trace("Anonymous user is being granted access to unprotected operation.");
@@ -110,7 +110,7 @@ public Object authorizeMethod(InvocationContext context) throws Exception {
110110
throw new UnauthorizedException("User is not authenticated.");
111111
}
112112

113-
log.trace(" principalId:" + securityIdentity.getPrincipal().getName());
113+
log.trace("principalId:" + securityIdentity.getPrincipal().getName());
114114

115115
// If the user is an admin (via the admin-override check) then there's no need to
116116
// check rbac or obac.
@@ -119,6 +119,11 @@ public Object authorizeMethod(InvocationContext context) throws Exception {
119119
return context.proceed();
120120
}
121121

122+
// If Authenticated read access is enabled, and the operation is read, allow it.
123+
if (authConfig.authenticatedReadAccessEnabled && (annotation.level() == AuthorizedLevel.Read) || annotation.level() == AuthorizedLevel.None) {
124+
return context.proceed();
125+
}
126+
122127
// If RBAC is enabled, apply role based rules
123128
if (authConfig.roleBasedAuthorizationEnabled && !rbac.isAuthorized(context)) {
124129
log.warn("RBAC enabled and required role missing.");

app/src/main/resources/application.properties

+1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ registry.auth.role-based-authorization=${REGISTRY_AUTH_RBAC_ENABLED:${registry-l
167167
registry.auth.owner-only-authorization=${REGISTRY_AUTH_OBAC_ENABLED:${registry-legacy.auth.owner-only-authorization}}
168168
registry.auth.owner-only-authorization.limit-group-access=${REGISTRY_AUTH_OBAC_LIMIT_GROUP_ACCESS:false}
169169
registry.auth.anonymous-read-access.enabled=${REGISTRY_AUTH_ANONYMOUS_READS_ENABLED:${registry-legacy.auth.anonymous-read-access.enabled}}
170+
registry.auth.authenticated-read-access.enabled=${REGISTRY_AUTH_AUTHENTICATED_READS_ENABLED:false}
170171
registry.auth.roles.readonly=${REGISTRY_AUTH_ROLES_READONLY:sr-readonly}
171172
registry.auth.roles.developer=${REGISTRY_AUTH_ROLES_DEVELOPER:sr-developer}
172173
registry.auth.roles.admin=${REGISTRY_AUTH_ROLES_ADMIN:sr-admin}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2021 Red Hat
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.apicurio.registry.auth;
18+
19+
/**
20+
* @author carnalca@redhat.com
21+
*/
22+
23+
import io.apicurio.registry.AbstractResourceTestBase;
24+
import io.apicurio.registry.rest.client.RegistryClient;
25+
import io.apicurio.registry.rest.client.RegistryClientFactory;
26+
import io.apicurio.registry.rest.v2.beans.ArtifactSearchResults;
27+
import io.apicurio.registry.types.ArtifactType;
28+
import io.apicurio.registry.utils.tests.ApicurioTestTags;
29+
import io.apicurio.registry.utils.tests.AuthTestProfileAuthenticatedReadAccess;
30+
import io.apicurio.rest.client.auth.Auth;
31+
import io.apicurio.rest.client.auth.OidcAuth;
32+
import io.apicurio.rest.client.auth.exception.ForbiddenException;
33+
import io.quarkus.test.junit.QuarkusTest;
34+
import io.quarkus.test.junit.TestProfile;
35+
import org.eclipse.microprofile.config.inject.ConfigProperty;
36+
import org.junit.jupiter.api.Assertions;
37+
import org.junit.jupiter.api.Tag;
38+
import org.junit.jupiter.api.Test;
39+
40+
import java.io.ByteArrayInputStream;
41+
import java.io.InputStream;
42+
import java.nio.charset.StandardCharsets;
43+
import java.util.Collections;
44+
import java.util.Optional;
45+
46+
@QuarkusTest
47+
@TestProfile(AuthTestProfileAuthenticatedReadAccess.class)
48+
@Tag(ApicurioTestTags.DOCKER)
49+
public class AuthTestAuthenticatedReadAccess extends AbstractResourceTestBase {
50+
51+
@ConfigProperty(name = "registry.auth.token.endpoint")
52+
String authServerUrl;
53+
54+
String noRoleClientId = "registry-api-no-role";
55+
String adminClientId = "registry-api";
56+
57+
final String groupId = getClass().getSimpleName() + "Group";
58+
59+
private RegistryClient createClient(Auth auth) {
60+
return RegistryClientFactory.create(registryV2ApiUrl, Collections.emptyMap(), auth);
61+
}
62+
63+
@Override
64+
protected RegistryClient createRestClientV2() {
65+
Auth auth = new OidcAuth(authServerUrl, adminClientId, "test1", Optional.empty());
66+
return this.createClient(auth);
67+
}
68+
69+
@Test
70+
public void testReadOperationWithNoRole() throws Exception {
71+
// Read-only operation should work with credentials but no role.
72+
73+
Auth auth = new OidcAuth(authServerUrl, noRoleClientId, "test1", Optional.empty());
74+
RegistryClient client = this.createClient(auth);
75+
ArtifactSearchResults results = client.searchArtifacts(groupId, null, null, null, null, null, null, null, null);
76+
Assertions.assertTrue(results.getCount() >= 0);
77+
78+
// Write operation should fail with credentials but not role.
79+
InputStream data = new ByteArrayInputStream(("{\r\n" +
80+
" \"type\" : \"record\",\r\n" +
81+
" \"name\" : \"userInfo\",\r\n" +
82+
" \"namespace\" : \"my.example\",\r\n" +
83+
" \"fields\" : [{\"name\" : \"age\", \"type\" : \"int\"}]\r\n" +
84+
"}").getBytes(StandardCharsets.UTF_8));
85+
Assertions.assertThrows(ForbiddenException.class, () -> {
86+
client.createArtifact(groupId, "testReadOperationWithNoRole", ArtifactType.AVRO, data);
87+
});
88+
}
89+
}

distro/openshift-template/mt/apicurio-registry-mt-template.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ objects:
211211
value: ${REGISTRY_AUTH_OBAC_LIMIT_GROUP_ACCESS}
212212
- name: REGISTRY_AUTH_ANONYMOUS_READS_ENABLED
213213
value: ${REGISTRY_AUTH_ANONYMOUS_READS_ENABLED}
214+
- name: REGISTRY_AUTH_AUTHENTICATED_READS_ENABLED
215+
value: ${REGISTRY_AUTH_AUTHENTICATED_READS_ENABLED}
214216
- name: REGISTRY_AUTH_ROLES_READONLY
215217
value: ${REGISTRY_AUTH_ROLES_READONLY}
216218
- name: REGISTRY_AUTH_ROLES_DEVELOPER
@@ -552,6 +554,9 @@ parameters:
552554
- name: REGISTRY_AUTH_ANONYMOUS_READS_ENABLED
553555
description: flag to enable/disable anonymous read access
554556
value: "false"
557+
- name: REGISTRY_AUTH_AUTHENTICATED_READS_ENABLED
558+
description: flag to enable/disable authenticated read access
559+
value: "false"
555560
- name: REGISTRY_AUTH_ROLES_READONLY
556561
description: property to set the name of the role that enables ReadOnly access when using token based RBAC
557562
value: "sr-readonly"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2021 Red Hat
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.apicurio.registry.utils.tests;
18+
19+
import io.quarkus.test.junit.QuarkusTestProfile;
20+
21+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.Map;
24+
25+
/**
26+
* @author carnalca@redhat.com
27+
*/
28+
public class AuthTestProfileAuthenticatedReadAccess implements QuarkusTestProfile {
29+
30+
@Override
31+
public Map<String, String> getConfigOverrides() {
32+
return Map.of("registry.auth.authenticated-read-access.enabled", "true");
33+
}
34+
35+
@Override
36+
public List<TestResourceEntry> testResources() {
37+
return Collections.singletonList(
38+
new TestResourceEntry(KeycloakTestResource.class));
39+
}
40+
}

0 commit comments

Comments
 (0)