diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java index e21a1c5dc5..b99caaccef 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java @@ -21,8 +21,10 @@ import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +import org.keycloak.authorization.attribute.Attributes; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.policy.evaluation.Evaluation; +import org.keycloak.authorization.policy.evaluation.EvaluationContext; import org.keycloak.authorization.policy.provider.PolicyProvider; /** @@ -30,15 +32,25 @@ import org.keycloak.authorization.policy.provider.PolicyProvider; */ public class TimePolicyProvider implements PolicyProvider { - static String DEFAULT_DATE_PATTERN = "yyyy-MM-dd hh:mm:ss"; + static String DEFAULT_DATE_PATTERN = "yyyy-MM-dd HH:mm:ss"; + + static String CONTEXT_TIME_ENTRY = "kc.time.date_time"; @Override public void evaluate(Evaluation evaluation) { Policy policy = evaluation.getPolicy(); SimpleDateFormat dateFormat = new SimpleDateFormat(DEFAULT_DATE_PATTERN); - Date actualDate = new Date(); - try { + String contextTime = null; + EvaluationContext context = evaluation.getContext(); + if (context.getAttributes() != null && context.getAttributes().exists(CONTEXT_TIME_ENTRY)) { + Attributes.Entry contextTimeEntry = context.getAttributes().getValue(CONTEXT_TIME_ENTRY); + if (!contextTimeEntry.isEmpty()) { + contextTime = contextTimeEntry.asString(0); + } + } + Date actualDate = contextTime == null ? new Date() : dateFormat.parse(contextTime); + String notBefore = policy.getConfig().get("nbf"); if (notBefore != null && !"".equals(notBefore)) { if (actualDate.before(dateFormat.parse(format(notBefore)))) { diff --git a/services/src/main/java/org/keycloak/authorization/common/DefaultEvaluationContext.java b/services/src/main/java/org/keycloak/authorization/common/DefaultEvaluationContext.java index 2551639fad..dec33cd6a7 100644 --- a/services/src/main/java/org/keycloak/authorization/common/DefaultEvaluationContext.java +++ b/services/src/main/java/org/keycloak/authorization/common/DefaultEvaluationContext.java @@ -53,7 +53,7 @@ public class DefaultEvaluationContext implements EvaluationContext { public Map> getBaseAttributes() { HashMap> attributes = new HashMap<>(); - attributes.put("kc.time.date_time", Arrays.asList(new SimpleDateFormat("MM/dd/yyyy hh:mm:ss").format(new Date()))); + attributes.put("kc.time.date_time", Arrays.asList(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()))); attributes.put("kc.client.network.ip_address", Arrays.asList(this.keycloakSession.getContext().getConnection().getRemoteAddr())); attributes.put("kc.client.network.host", Arrays.asList(this.keycloakSession.getContext().getConnection().getRemoteHost())); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java index 64431fec2d..bedd4a70d5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java @@ -16,7 +16,10 @@ */ package org.keycloak.testsuite.authz; +import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -54,6 +57,7 @@ import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.authorization.JSPolicyRepresentation; +import org.keycloak.representations.idm.authorization.TimePolicyRepresentation; import org.keycloak.testsuite.runonserver.RunOnServerDeployment; import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.GroupBuilder; @@ -123,6 +127,39 @@ public class PolicyEvaluationTest extends AbstractAuthzTest { return RunOnServerDeployment.create(AbstractAuthzTest.class); } + @Test + public void testCheckDateAndTime() {testingClient.server().run(PolicyEvaluationTest::testCheckDateAndTime);} + + public static void testCheckDateAndTime(KeycloakSession session) { + session.getContext().setRealm(session.realms().getRealmByName("authz-test")); + AuthorizationProvider authorization = session.getProvider(AuthorizationProvider.class); + ClientModel clientModel = session.realms().getClientByClientId("resource-server-test", session.getContext().getRealm()); + StoreFactory storeFactory = authorization.getStoreFactory(); + ResourceServer resourceServer = storeFactory.getResourceServerStore().findById(clientModel.getId()); + TimePolicyRepresentation policyRepresentation = new TimePolicyRepresentation(); + policyRepresentation.setName("testCheckDateAndTime"); + + // set the notOnOrAfter for 1 hour from now + long notOnOrAfter = System.currentTimeMillis() + 3600000; + Date notOnOrAfterDate = new Date(notOnOrAfter); + policyRepresentation.setNotOnOrAfter(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(notOnOrAfterDate)); + + // evaluation should succeed with the default context as it uses the current time as the date to be compared. + Policy policy = storeFactory.getPolicyStore().create(policyRepresentation, resourceServer); + PolicyProvider provider = authorization.getProvider(policy.getType()); + DefaultEvaluation evaluation = createEvaluation(session, authorization, resourceServer, policy); + provider.evaluate(evaluation); + Assert.assertEquals(Effect.PERMIT, evaluation.getEffect()); + + // lets now override the context to use a time that exceeds the time that was set in the policy. + long contextTime = System.currentTimeMillis() + 5400000; + Map> attributes = new HashMap<>(); + attributes.put("kc.time.date_time", Arrays.asList(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(contextTime)))); + evaluation = createEvaluation(session, authorization, null, resourceServer, policy, attributes); + provider.evaluate(evaluation); + Assert.assertEquals(Effect.DENY, evaluation.getEffect()); + } + @Test public void testCheckUserInGroup() { testingClient.server().run(PolicyEvaluationTest::testCheckUserInGroup); @@ -599,6 +636,12 @@ public class PolicyEvaluationTest extends AbstractAuthzTest { } private static DefaultEvaluation createEvaluation(KeycloakSession session, AuthorizationProvider authorization, Resource resource, ResourceServer resourceServer, Policy policy) { + return createEvaluation(session, authorization, resource, resourceServer, policy, null); + } + + private static DefaultEvaluation createEvaluation(KeycloakSession session, AuthorizationProvider authorization, + Resource resource, ResourceServer resourceServer, Policy policy, + Map> contextAttributes) { return new DefaultEvaluation(new ResourcePermission(resource, null, resourceServer), new DefaultEvaluationContext(new Identity() { @Override public String getId() { @@ -609,8 +652,19 @@ public class PolicyEvaluationTest extends AbstractAuthzTest { public Attributes getAttributes() { return null; } - }, session), policy, policy, evaluation -> { + }, session) { - }, authorization); + /* + * Allow specific tests to override/add attributes to the context. + */ + @Override + public Map> getBaseAttributes() { + Map> baseAttributes = super.getBaseAttributes(); + if (contextAttributes != null) { + baseAttributes.putAll(contextAttributes); + } + return baseAttributes; + } + }, policy, policy, evaluation -> {}, authorization); } }