diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/regex/RegexPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/regex/RegexPolicyProvider.java new file mode 100644 index 0000000000..bdf2c6b02c --- /dev/null +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/regex/RegexPolicyProvider.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.keycloak.authorization.policy.provider.regex; + +import java.util.function.BiFunction; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.attribute.Attributes; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.policy.evaluation.Evaluation; +import org.keycloak.authorization.policy.provider.PolicyProvider; +import org.keycloak.representations.idm.authorization.RegexPolicyRepresentation; + +/** + * @author Yoshiyuki Tabata + */ +public class RegexPolicyProvider implements PolicyProvider { + + private final BiFunction representationFunction; + + public RegexPolicyProvider(BiFunction representationFunction) { + this.representationFunction = representationFunction; + } + + @Override + public void close() { + } + + @Override + public void evaluate(Evaluation evaluation) { + AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider(); + RegexPolicyRepresentation policy = representationFunction.apply(evaluation.getPolicy(), authorizationProvider); + Attributes.Entry targetClaim = evaluation.getContext().getIdentity().getAttributes().getValue(policy.getTargetClaim()); + + if (targetClaim == null) { + return; + } + + Pattern pattern = Pattern.compile(policy.getPattern()); + Matcher matcher = pattern.matcher(targetClaim.asString(0)); + if (matcher.matches()) { + evaluation.grant(); + } + } + +} diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/regex/RegexPolicyProviderFactory.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/regex/RegexPolicyProviderFactory.java new file mode 100644 index 0000000000..3ab77816cf --- /dev/null +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/regex/RegexPolicyProviderFactory.java @@ -0,0 +1,116 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.keycloak.authorization.policy.provider.regex; + +import java.util.HashMap; +import java.util.Map; + +import org.keycloak.Config.Scope; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.Policy; +import org.keycloak.authorization.policy.provider.PolicyProvider; +import org.keycloak.authorization.policy.provider.PolicyProviderFactory; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.RegexPolicyRepresentation; + +/** + * @author Yoshiyuki Tabata + */ +public class RegexPolicyProviderFactory implements PolicyProviderFactory { + + private RegexPolicyProvider provider = new RegexPolicyProvider(this::toRepresentation); + + @Override + public PolicyProvider create(KeycloakSession session) { + return provider; + } + + @Override + public void init(Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return "regex"; + } + + @Override + public String getName() { + return "Regex"; + } + + @Override + public String getGroup() { + return "Identity Based"; + } + + @Override + public PolicyProvider create(AuthorizationProvider authorization) { + return provider; + } + + @Override + public RegexPolicyRepresentation toRepresentation(Policy policy, AuthorizationProvider authorization) { + RegexPolicyRepresentation representation = new RegexPolicyRepresentation(); + Map config = policy.getConfig(); + + representation.setTargetClaim(config.get("targetClaim")); + representation.setPattern(config.get("pattern")); + + return representation; + } + + @Override + public Class getRepresentationType() { + return RegexPolicyRepresentation.class; + } + + @Override + public void onCreate(Policy policy, RegexPolicyRepresentation representation, AuthorizationProvider authorization) { + updatePolicy(policy, representation); + } + + @Override + public void onUpdate(Policy policy, RegexPolicyRepresentation representation, AuthorizationProvider authorization) { + updatePolicy(policy, representation); + } + + @Override + public void onImport(Policy policy, PolicyRepresentation representation, AuthorizationProvider authorization) { + policy.setConfig(representation.getConfig()); + } + + private void updatePolicy(Policy policy, RegexPolicyRepresentation representation) { + Map config = new HashMap<>(policy.getConfig()); + + config.put("targetClaim", representation.getTargetClaim()); + config.put("pattern", representation.getPattern()); + + policy.setConfig(config); + } +} diff --git a/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory b/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory index d5f00400e4..cff2ca8462 100644 --- a/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory +++ b/authz/policy/common/src/main/resources/META-INF/services/org.keycloak.authorization.policy.provider.PolicyProviderFactory @@ -44,4 +44,5 @@ org.keycloak.authorization.policy.provider.user.UserPolicyProviderFactory org.keycloak.authorization.policy.provider.client.ClientPolicyProviderFactory org.keycloak.authorization.policy.provider.group.GroupPolicyProviderFactory org.keycloak.authorization.policy.provider.permission.UMAPolicyProviderFactory -org.keycloak.authorization.policy.provider.clientscope.ClientScopePolicyProviderFactory \ No newline at end of file +org.keycloak.authorization.policy.provider.clientscope.ClientScopePolicyProviderFactory +org.keycloak.authorization.policy.provider.regex.RegexPolicyProviderFactory \ No newline at end of file diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/RegexPolicyRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/authorization/RegexPolicyRepresentation.java new file mode 100644 index 0000000000..4dc39401b1 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/authorization/RegexPolicyRepresentation.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.representations.idm.authorization; + +/** + * @author Yoshiyuki Tabata + */ +public class RegexPolicyRepresentation extends AbstractPolicyRepresentation { + + private String targetClaim; + private String pattern; + + @Override + public String getType() { + return "regex"; + } + + public String getTargetClaim() { + return targetClaim; + } + + public void setTargetClaim(String targetClaim) { + this.targetClaim = targetClaim; + } + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + +} diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java index 5a6f7c39d0..45bee4b858 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/PoliciesResource.java @@ -106,4 +106,7 @@ public interface PoliciesResource { @Path("client-scope") ClientScopePoliciesResource clientScope(); + + @Path("regex") + RegexPoliciesResource regex(); } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RegexPoliciesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RegexPoliciesResource.java new file mode 100644 index 0000000000..10574e21d0 --- /dev/null +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RegexPoliciesResource.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.admin.client.resource; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.keycloak.representations.idm.authorization.RegexPolicyRepresentation; + +/** + * @author Yoshiyuki Tabata + */ +public interface RegexPoliciesResource { + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + Response create(RegexPolicyRepresentation representation); + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RegexPolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RegexPolicyTest.java new file mode 100644 index 0000000000..ab89c975f4 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/RegexPolicyTest.java @@ -0,0 +1,196 @@ +/* + * Copyright 2021 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.authz; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.resource.AuthorizationResource; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.ClientsResource; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.authorization.client.AuthorizationDeniedException; +import org.keycloak.authorization.client.AuthzClient; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; +import org.keycloak.protocol.oidc.mappers.UserAttributeMapper; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.authorization.AuthorizationRequest; +import org.keycloak.representations.idm.authorization.AuthorizationResponse; +import org.keycloak.representations.idm.authorization.PermissionRequest; +import org.keycloak.representations.idm.authorization.RegexPolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; +import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer; +import org.keycloak.testsuite.util.ClientBuilder; +import org.keycloak.testsuite.util.RealmBuilder; +import org.keycloak.testsuite.util.UserBuilder; + +/** + * @author Yoshiyuki Tabata + */ +@AuthServerContainerExclude(AuthServer.REMOTE) +public class RegexPolicyTest extends AbstractAuthzTest { + + @Override + public void addTestRealms(List testRealms) { + ProtocolMapperRepresentation userAttrFooProtocolMapper = new ProtocolMapperRepresentation(); + userAttrFooProtocolMapper.setName("userAttrFoo"); + userAttrFooProtocolMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); + userAttrFooProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + Map configFoo = new HashMap<>(); + configFoo.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + configFoo.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + configFoo.put(OIDCAttributeMapperHelper.JSON_TYPE, "String"); + configFoo.put("user.attribute", "foo"); + configFoo.put("claim.name", "foo"); + userAttrFooProtocolMapper.setConfig(configFoo); + + ProtocolMapperRepresentation userAttrBarProtocolMapper = new ProtocolMapperRepresentation(); + userAttrBarProtocolMapper.setName("userAttrBar"); + userAttrBarProtocolMapper.setProtocolMapper(UserAttributeMapper.PROVIDER_ID); + userAttrBarProtocolMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + Map configBar = new HashMap<>(); + configBar.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + configBar.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); + configBar.put(OIDCAttributeMapperHelper.JSON_TYPE, "String"); + configBar.put("user.attribute", "bar"); + configBar.put("claim.name", "bar"); + userAttrBarProtocolMapper.setConfig(configBar); + + testRealms.add(RealmBuilder.create().name("authz-test") + .user(UserBuilder.create().username("marta").password("password").addAttribute("foo", "foo").addAttribute("bar", + "barbar")) + .user(UserBuilder.create().username("taro").password("password").addAttribute("foo", "faa").addAttribute("bar", + "bbarbar")) + .client(ClientBuilder.create().clientId("resource-server-test").secret("secret").authorizationServicesEnabled(true) + .redirectUris("http://localhost/resource-server-test").directAccessGrants() + .protocolMapper(userAttrFooProtocolMapper, userAttrBarProtocolMapper)) + .build()); + } + + @Before + public void configureAuthorization() throws Exception { + createResource("Resource A"); + createResource("Resource B"); + + createRegexPolicy("Regex foo Policy", "foo", "foo"); + createRegexPolicy("Regex bar Policy", "bar", "^bar.+$"); + + createResourcePermission("Resource A Permission", "Resource A", "Regex foo Policy"); + createResourcePermission("Resource B Permission", "Resource B", "Regex bar Policy"); + } + + private void createResource(String name) { + AuthorizationResource authorization = getClient().authorization(); + ResourceRepresentation resource = new ResourceRepresentation(name); + + authorization.resources().create(resource).close(); + } + + private void createRegexPolicy(String name, String targetClaim, String pattern) { + RegexPolicyRepresentation policy = new RegexPolicyRepresentation(); + + policy.setName(name); + policy.setTargetClaim(targetClaim); + policy.setPattern(pattern); + + getClient().authorization().policies().regex().create(policy).close(); + } + + private void createResourcePermission(String name, String resource, String... policies) { + ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation(); + + permission.setName(name); + permission.addResource(resource); + permission.addPolicy(policies); + + getClient().authorization().permissions().resource().create(permission).close(); + } + + private ClientResource getClient() { + return getClient(getRealm()); + } + + private ClientResource getClient(RealmResource realm) { + ClientsResource clients = realm.clients(); + return clients.findByClientId("resource-server-test").stream() + .map(representation -> clients.get(representation.getId())).findFirst() + .orElseThrow(() -> new RuntimeException("Expected client [resource-server-test]")); + } + + private RealmResource getRealm() { + try { + return getAdminClient().realm("authz-test"); + } catch (Exception e) { + throw new RuntimeException("Failed to create admin client"); + } + } + + @Test + public void testWithExpectedUserAttribute() { + // Access Resource A with marta. + AuthzClient authzClient = getAuthzClient(); + PermissionRequest request = new PermissionRequest("Resource A"); + String ticket = authzClient.protection().permission().create(request).getTicket(); + AuthorizationResponse response = authzClient.authorization("marta", "password") + .authorize(new AuthorizationRequest(ticket)); + assertNotNull(response.getToken()); + + // Access Resource B with marta. + request = new PermissionRequest("Resource B"); + ticket = authzClient.protection().permission().create(request).getTicket(); + response = authzClient.authorization("marta", "password").authorize(new AuthorizationRequest(ticket)); + assertNotNull(response.getToken()); + } + + @Test + public void testWithoutExpectedUserAttribute() { + // Access Resource A with taro. + AuthzClient authzClient = getAuthzClient(); + PermissionRequest request = new PermissionRequest("Resource A"); + String ticket = authzClient.protection().permission().create(request).getTicket(); + try { + authzClient.authorization("taro", "password").authorize(new AuthorizationRequest(ticket)); + fail("Should fail."); + } catch (AuthorizationDeniedException ignore) { + + } + + // Access Resource B with taro. + request = new PermissionRequest("Resource B"); + ticket = authzClient.protection().permission().create(request).getTicket(); + try { + authzClient.authorization("taro", "password").authorize(new AuthorizationRequest(ticket)); + fail("Should fail."); + } catch (AuthorizationDeniedException ignore) { + + } + } + + private AuthzClient getAuthzClient() { + return AuthzClient.create(getClass().getResourceAsStream("/authorization-test/default-keycloak.json")); + } +} diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index ecb7e94eae..f837cb51a3 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1692,6 +1692,13 @@ authz-add-client-scope-policy=Add Client Scope Policy authz-no-client-scopes-assigned=No client scopes assigned. authz-policy-client-scope-client-scopes.tooltip=Specifies which client scope(s) are allowed by this policy. select-a-client-scope=Select a client scope +# Authz Regex Policy Detail +authz-add-regex-policy=Add Regex Policy +regex=Regex +authz-policy-target-claim=Target Claim +authz-policy-target-claim.tooltip=Specifies the target claim which the policy will fetch. +authz-policy-regex-pattern=Regex Pattern +authz-policy-regex-pattern.tooltip=Specifies the regex pattern. # Authz Permission List authz-no-permissions-available=No permissions available. diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js index 5c4e29b2e0..8e64c03c8e 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-app.js @@ -418,6 +418,28 @@ module.config(['$routeProvider', function ($routeProvider) { } }, controller: 'ResourceServerPolicyClientScopeDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/regex/create', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-regex-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyRegexDetailCtrl' + }).when('/realms/:realm/clients/:client/authz/resource-server/policy/regex/:id', { + templateUrl: resourceUrl + '/partials/authz/policy/provider/resource-server-policy-regex-detail.html', + resolve: { + realm: function (RealmLoader) { + return RealmLoader(); + }, + client : function(ClientLoader) { + return ClientLoader(); + } + }, + controller: 'ResourceServerPolicyRegexDetailCtrl' }).when('/realms/:realm/roles/:role/permissions', { templateUrl : resourceUrl + '/partials/authz/mgmt/realm-role-permissions.html', resolve : { diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js index f4c533d273..c8d8547827 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js @@ -2192,6 +2192,28 @@ module.controller('ResourceServerPolicyClientScopeDetailCtrl', function($scope, }, realm, client, $scope); }); +module.controller('ResourceServerPolicyRegexDetailCtrl', function($scope, realm, client, PolicyController) { + PolicyController.onInit({ + getPolicyType : function() { + return "regex"; + }, + + onInit : function() { + }, + + onInitUpdate : function(policy) { + }, + + onUpdate : function() { + delete $scope.policy.config; + }, + + onCreate : function() { + delete $scope.policy.config; + } + }, realm, client, $scope); +}); + module.service("PolicyController", function($http, $route, $location, ResourceServer, ResourceServerPolicy, ResourceServerPermission, AuthzDialog, Notifications, policyViewState, PolicyProvider, viewState) { var PolicyController = {}; diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-regex-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-regex-detail.html new file mode 100644 index 0000000000..83c6bbf3b1 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-regex-detail.html @@ -0,0 +1,100 @@ + + +
+ + + +

{{:: 'authz-add-regex-policy' | translate}}

+

+ {{originalPolicy.name|capitalize}} +

+ +
+
+
+ +
+ +
+ {{:: 'authz-policy-name.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-description.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-target-claim.tooltip' | translate}} +
+
+ +
+ +
+ {{:: 'authz-policy-regex-pattern.tooltip' | translate}} +
+
+ + +
+ +
+ + {{:: 'authz-policy-logic.tooltip' | translate}} +
+ +
+
+
+ + +
+
+
+
+ + \ No newline at end of file