Skip to content

Commit 6775bf1

Browse files
authored
Merge pull request #44344 from sberyozkin/add_permission
Add permission checker shortcuts to QuarkusSecurityIdentity.Builder
2 parents 0037f52 + 94f7050 commit 6775bf1

File tree

5 files changed

+167
-65
lines changed

5 files changed

+167
-65
lines changed

docs/src/main/asciidoc/security-authorize-web-endpoints-reference.adoc

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,6 @@ In the following example, xref:security-customization.adoc#security-identity-cus
10081008
[source,java]
10091009
----
10101010
import java.security.Permission;
1011-
import java.util.function.Function;
10121011
10131012
import jakarta.enterprise.context.ApplicationScoped;
10141013
@@ -1034,24 +1033,15 @@ public class PermissionsIdentityAugmentor implements SecurityIdentityAugmentor {
10341033
}
10351034
10361035
SecurityIdentity build(SecurityIdentity identity) {
1037-
Permission possessedPermission = new MediaLibraryPermission("media-library",
1038-
new String[] { "read", "write", "list"}); <1>
10391036
return QuarkusSecurityIdentity.builder(identity)
1040-
.addPermissionChecker(new Function<Permission, Uni<Boolean>>() { <2>
1041-
@Override
1042-
public Uni<Boolean> apply(Permission requiredPermission) {
1043-
boolean accessGranted = possessedPermission.implies(requiredPermission);
1044-
return Uni.createFrom().item(accessGranted);
1045-
}
1046-
})
1037+
.addPermission(new MediaLibraryPermission("media-library", new String[] { "read", "write", "list"}); <1>
10471038
.build();
10481039
}
10491040
10501041
}
10511042
----
1052-
<1> The permission `media-library` that was created can perform `read`, `write`, and `list` actions.
1043+
<1> Add a `media-library` permission that was created can perform `read`, `write`, and `list` actions.
10531044
Because `MediaLibrary` is the `TvLibrary` class parent, a user with the `admin` role is also permitted to modify `TvLibrary`.
1054-
<2> You can add a permission checker through `io.quarkus.security.runtime.QuarkusSecurityIdentity.Builder#addPermissionChecker`.
10551045

10561046
CAUTION: Annotation-based permissions do not work with custom xref:security-customization.adoc#jaxrs-security-context[Jakarta REST SecurityContexts] because there are no permissions in `jakarta.ws.rs.core.SecurityContext`.
10571047

@@ -1076,7 +1066,7 @@ import jakarta.ws.rs.Path;
10761066
import org.jboss.resteasy.reactive.RestForm;
10771067
import org.jboss.resteasy.reactive.RestPath;
10781068
1079-
@Path("/project")
1069+
@Path("/project/{projectName}")
10801070
public class ProjectResource {
10811071
10821072
@PermissionsAllowed("rename-project") <1>
@@ -1129,7 +1119,7 @@ public class ProjectPermissionChecker {
11291119
<1> A CDI bean with the permission checker must be either a normal scoped bean or a `@Singleton` bean.
11301120
<2> The permission checker method must return either `boolean` or `Uni<Boolean>`. Private checker methods are not supported.
11311121

1132-
TIP: Permission checks run by default on event loops whenever possible.
1122+
TIP: Permission checks run by default on event loops.
11331123
Annotate a permission checker method with the `io.smallrye.common.annotation.Blocking` annotation if you want to run the check on a worker thread.
11341124

11351125
[[permission-meta-annotation]]

extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,20 @@
77
import java.security.Key;
88
import java.security.MessageDigest;
99
import java.security.NoSuchAlgorithmException;
10-
import java.security.Permission;
1110
import java.util.ArrayList;
1211
import java.util.Arrays;
1312
import java.util.Base64;
1413
import java.util.Collection;
1514
import java.util.Collections;
1615
import java.util.Comparator;
16+
import java.util.HashSet;
1717
import java.util.LinkedList;
1818
import java.util.List;
1919
import java.util.Map;
2020
import java.util.SortedMap;
2121
import java.util.StringTokenizer;
2222
import java.util.TreeMap;
2323
import java.util.function.Consumer;
24-
import java.util.function.Function;
2524
import java.util.regex.Pattern;
2625

2726
import javax.crypto.SecretKey;
@@ -50,7 +49,6 @@
5049
import io.quarkus.oidc.common.runtime.OidcConstants;
5150
import io.quarkus.oidc.runtime.providers.KnownOidcProviders;
5251
import io.quarkus.security.AuthenticationFailedException;
53-
import io.quarkus.security.StringPermission;
5452
import io.quarkus.security.credential.TokenCredential;
5553
import io.quarkus.security.identity.AuthenticationRequestContext;
5654
import io.quarkus.security.identity.SecurityIdentity;
@@ -393,38 +391,8 @@ static void setSecurityIdentityPermissions(QuarkusSecurityIdentity.Builder build
393391

394392
static void addTokenScopesAsPermissions(Builder builder, Collection<String> scopes) {
395393
if (!scopes.isEmpty()) {
396-
builder.addPermissionChecker(new Function<Permission, Uni<Boolean>>() {
397-
398-
private final Permission[] permissions = transformScopesToPermissions(scopes);
399-
400-
@Override
401-
public Uni<Boolean> apply(Permission requiredPermission) {
402-
for (Permission possessedPermission : permissions) {
403-
if (possessedPermission.implies(requiredPermission)) {
404-
// access granted
405-
return Uni.createFrom().item(Boolean.TRUE);
406-
}
407-
}
408-
// access denied
409-
return Uni.createFrom().item(Boolean.FALSE);
410-
}
411-
});
412-
}
413-
}
414-
415-
static Permission[] transformScopesToPermissions(Collection<String> scopes) {
416-
final Permission[] permissions = new Permission[scopes.size()];
417-
int i = 0;
418-
for (String scope : scopes) {
419-
int semicolonIndex = scope.indexOf(':');
420-
if (semicolonIndex > 0 && semicolonIndex < scope.length() - 1) {
421-
permissions[i++] = new StringPermission(scope.substring(0, semicolonIndex),
422-
scope.substring(semicolonIndex + 1));
423-
} else {
424-
permissions[i++] = new StringPermission(scope);
425-
}
394+
builder.addPermissionsAsString(new HashSet<>(scopes));
426395
}
427-
return permissions;
428396
}
429397

430398
public static void setSecurityIdentityRoles(QuarkusSecurityIdentity.Builder builder, OidcTenantConfig config,

extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcUtilsTest.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import java.io.InputStream;
1212
import java.io.InputStreamReader;
1313
import java.nio.charset.StandardCharsets;
14-
import java.security.Permission;
1514
import java.util.Arrays;
1615
import java.util.Collections;
1716
import java.util.HashMap;
@@ -264,22 +263,6 @@ public void testDecodeJwt() throws Exception {
264263
assertTrue(json.containsKey("jti"));
265264
}
266265

267-
@Test
268-
public void testTransformScopeToPermission() throws Exception {
269-
Permission[] perms = OidcUtils.transformScopesToPermissions(
270-
List.of("read", "read:d", "read:", ":read"));
271-
assertEquals(4, perms.length);
272-
273-
assertEquals("read", perms[0].getName());
274-
assertNull(perms[0].getActions());
275-
assertEquals("read", perms[1].getName());
276-
assertEquals("d", perms[1].getActions());
277-
assertEquals("read:", perms[2].getName());
278-
assertNull(perms[2].getActions());
279-
assertEquals(":read", perms[3].getName());
280-
assertNull(perms[3].getActions());
281-
}
282-
283266
@Test
284267
public void testEncodeScopesOpenidAdded() throws Exception {
285268
OidcTenantConfig config = new OidcTenantConfig();

extensions/security/runtime/src/main/java/io/quarkus/security/runtime/QuarkusSecurityIdentity.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import java.util.Map;
1111
import java.util.Set;
1212
import java.util.function.Function;
13+
import java.util.stream.Collectors;
1314

15+
import io.quarkus.security.StringPermission;
1416
import io.quarkus.security.credential.Credential;
1517
import io.quarkus.security.identity.SecurityIdentity;
1618
import io.smallrye.mutiny.Uni;
@@ -144,6 +146,7 @@ public static class Builder {
144146
Principal principal;
145147
Set<String> roles = new HashSet<>();
146148
Set<Credential> credentials = new HashSet<>();
149+
Set<Permission> permissions = new HashSet<>();
147150
Map<String, Object> attributes = new HashMap<>();
148151
List<Function<Permission, Uni<Boolean>>> permissionCheckers = new ArrayList<>();
149152
private boolean anonymous;
@@ -205,6 +208,48 @@ public Builder addAttributes(Map<String, Object> attributes) {
205208
return this;
206209
}
207210

211+
/**
212+
* Adds a permission as String.
213+
*
214+
* @param permission The permission in a String format.
215+
* @return This builder
216+
*/
217+
public Builder addPermissionAsString(String permission) {
218+
return addPermissionsAsString(Set.of(permission));
219+
}
220+
221+
/**
222+
* Adds permissions as String
223+
*
224+
* @param permissions The permissions in a String format.
225+
* @return This builder
226+
*/
227+
public Builder addPermissionsAsString(Set<String> permissions) {
228+
return addPermissions(permissions.stream().map(p -> toPermission(p))
229+
.collect(Collectors.toSet()));
230+
}
231+
232+
/**
233+
* Adds a permission.
234+
*
235+
* @param permission The permission
236+
* @return This builder
237+
*/
238+
public Builder addPermission(Permission permission) {
239+
return addPermissions(Set.of(permission));
240+
}
241+
242+
/**
243+
* Adds permissions.
244+
*
245+
* @param permissions The permissions
246+
* @return This builder
247+
*/
248+
public Builder addPermissions(Set<Permission> permissions) {
249+
this.permissions.addAll(permissions);
250+
return this;
251+
}
252+
208253
/**
209254
* Adds a permission checker function. This permission checker has the following semantics:
210255
*
@@ -258,9 +303,43 @@ public QuarkusSecurityIdentity build() {
258303
if (principal == null && !anonymous) {
259304
throw new IllegalStateException("Principal is null but anonymous status is false");
260305
}
306+
addPossesedPermissionsChecker();
261307

262308
built = true;
263309
return new QuarkusSecurityIdentity(this);
264310
}
311+
312+
private void addPossesedPermissionsChecker() {
313+
if (!permissions.isEmpty()) {
314+
addPermissionChecker(
315+
new Function<Permission, Uni<Boolean>>() {
316+
317+
@Override
318+
public Uni<Boolean> apply(Permission requiredPermission) {
319+
320+
for (Permission possessedPermission : permissions) {
321+
if (possessedPermission.implies(requiredPermission)) {
322+
return Uni.createFrom().item(true);
323+
}
324+
}
325+
return Uni.createFrom().item(false);
326+
327+
}
328+
});
329+
}
330+
331+
}
332+
333+
static Permission toPermission(String permissionAsString) {
334+
int semicolonIndex = permissionAsString.indexOf(':');
335+
if (semicolonIndex > 0 && semicolonIndex < permissionAsString.length() - 1) {
336+
return new StringPermission(permissionAsString.substring(0, semicolonIndex),
337+
permissionAsString.substring(semicolonIndex + 1));
338+
} else {
339+
return new StringPermission(permissionAsString);
340+
}
341+
342+
}
265343
}
344+
266345
}

extensions/security/runtime/src/test/java/io/quarkus/security/runtime/QuarkusSecurityIdentityTest.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.quarkus.security.runtime;
22

3+
import static io.quarkus.security.runtime.QuarkusSecurityIdentity.Builder.toPermission;
34
import static org.junit.jupiter.api.Assertions.assertEquals;
45
import static org.junit.jupiter.api.Assertions.assertFalse;
56
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -14,13 +15,94 @@
1415

1516
import org.junit.jupiter.api.Test;
1617

18+
import io.quarkus.security.StringPermission;
1719
import io.quarkus.security.credential.Credential;
1820
import io.quarkus.security.credential.PasswordCredential;
1921
import io.quarkus.security.identity.SecurityIdentity;
2022
import io.smallrye.mutiny.Uni;
2123

2224
public class QuarkusSecurityIdentityTest {
2325

26+
@Test
27+
public void testAddPermissionAsString() throws Exception {
28+
SecurityIdentity identity = QuarkusSecurityIdentity.builder()
29+
.setPrincipal(new QuarkusPrincipal("alice"))
30+
.addPermissionAsString("read")
31+
.build();
32+
33+
assertTrue(identity.checkPermissionBlocking(new StringPermission("read")));
34+
assertFalse(identity.checkPermissionBlocking(new StringPermission("write")));
35+
}
36+
37+
@Test
38+
public void testAddPermissionWithActionAsString() throws Exception {
39+
SecurityIdentity identity = QuarkusSecurityIdentity.builder()
40+
.setPrincipal(new QuarkusPrincipal("alice"))
41+
.addPermissionAsString("read:singledoc")
42+
.build();
43+
44+
assertTrue(identity.checkPermissionBlocking(new StringPermission("read", "singledoc")));
45+
assertFalse(identity.checkPermissionBlocking(new StringPermission("read", "all")));
46+
assertFalse(identity.checkPermissionBlocking(new StringPermission("write")));
47+
}
48+
49+
@Test
50+
public void testAddPermissionsAsString() throws Exception {
51+
SecurityIdentity identity = QuarkusSecurityIdentity.builder()
52+
.setPrincipal(new QuarkusPrincipal("alice"))
53+
.addPermissionsAsString(Set.of("read", "write"))
54+
.build();
55+
56+
assertTrue(identity.checkPermissionBlocking(new StringPermission("read")));
57+
assertTrue(identity.checkPermissionBlocking(new StringPermission("write")));
58+
assertFalse(identity.checkPermissionBlocking(new StringPermission("comment")));
59+
}
60+
61+
@Test
62+
public void testAddPermissionsWithActionAsString() throws Exception {
63+
SecurityIdentity identity = QuarkusSecurityIdentity.builder()
64+
.setPrincipal(new QuarkusPrincipal("alice"))
65+
.addPermissionsAsString(Set.of("read:singledoc", "write:singledoc"))
66+
.build();
67+
68+
assertTrue(identity.checkPermissionBlocking(new StringPermission("read", "singledoc")));
69+
assertTrue(identity.checkPermissionBlocking(new StringPermission("write", "singledoc")));
70+
assertFalse(identity.checkPermissionBlocking(new StringPermission("read:all")));
71+
assertFalse(identity.checkPermissionBlocking(new StringPermission("write:all")));
72+
assertFalse(identity.checkPermissionBlocking(new StringPermission("comment")));
73+
}
74+
75+
@Test
76+
public void testAddPermission() throws Exception {
77+
SecurityIdentity identity = QuarkusSecurityIdentity.builder()
78+
.setPrincipal(new QuarkusPrincipal("alice"))
79+
.addPermission(new StringPermission("read"))
80+
.build();
81+
82+
assertTrue(identity.checkPermissionBlocking(new StringPermission("read")));
83+
assertFalse(identity.checkPermissionBlocking(new StringPermission("write")));
84+
}
85+
86+
@Test
87+
public void testAddPermissions() throws Exception {
88+
SecurityIdentity identity = QuarkusSecurityIdentity.builder()
89+
.setPrincipal(new QuarkusPrincipal("alice"))
90+
.addPermissions(Set.of(new StringPermission("read"), new StringPermission("write")))
91+
.build();
92+
93+
assertTrue(identity.checkPermissionBlocking(new StringPermission("read")));
94+
assertTrue(identity.checkPermissionBlocking(new StringPermission("write")));
95+
assertFalse(identity.checkPermissionBlocking(new StringPermission("comment")));
96+
}
97+
98+
@Test
99+
public void testConvertStringToPermission() throws Exception {
100+
assertEquals(toPermission("read"), new StringPermission("read"));
101+
assertEquals(toPermission("read:d"), new StringPermission("read", "d"));
102+
assertEquals(toPermission("read:"), new StringPermission("read:"));
103+
assertEquals(toPermission(":read"), new StringPermission(":read"));
104+
}
105+
24106
@Test
25107
public void testCopyIdentity() throws Exception {
26108
SecurityIdentity identity1 = QuarkusSecurityIdentity.builder()

0 commit comments

Comments
 (0)