io.helidon.security.abac.time.TimeValidator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of helidon-security-abac-time Show documentation
Show all versions of helidon-security-abac-time Show documentation
Time based authorization support.
/*
* Copyright (c) 2018, 2023 Oracle and/or its affiliates.
*
* 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 io.helidon.security.abac.time;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.time.DayOfWeek;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import io.helidon.common.Errors;
import io.helidon.common.config.Config;
import io.helidon.security.EndpointConfig;
import io.helidon.security.ProviderRequest;
import io.helidon.security.SecurityLevel;
import io.helidon.security.providers.abac.AbacAnnotation;
import io.helidon.security.providers.abac.AbacValidatorConfig;
import io.helidon.security.providers.abac.spi.AbacValidator;
/**
* Attribute validator for time based attribute checks.
* Currently supports:
*
* - between times (of a day - e.g. 8:30 - 17:00
* - days of week (e.g. MONDAY, TUESDAY)
*
*/
public final class TimeValidator implements AbacValidator {
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
private TimeValidator() {
}
/**
* Return a new instance of this validator with default configuration.
*
* @return new time validator
*/
public static TimeValidator create() {
return new TimeValidator();
}
@Override
public Class configClass() {
return TimeConfig.class;
}
@Override
public String configKey() {
return "time";
}
@Override
public TimeConfig fromConfig(Config config) {
return TimeConfig.create(config);
}
@Override
public TimeConfig fromAnnotations(EndpointConfig endpointConfig) {
TimeConfig.Builder builder = TimeConfig.builder();
for (SecurityLevel securityLevel : endpointConfig.securityLevels()) {
for (EndpointConfig.AnnotationScope scope : EndpointConfig.AnnotationScope.values()) {
List annotations = new ArrayList<>();
for (Class extends Annotation> annotation : supportedAnnotations()) {
annotations.addAll(securityLevel.filterAnnotations(annotation, scope));
}
for (Annotation annotation : annotations) {
if (annotation instanceof DaysOfWeek) {
DaysOfWeek daw = (DaysOfWeek) annotation;
for (DayOfWeek dayOfWeek : daw.value()) {
builder.addDaysOfWeek(dayOfWeek);
}
} else if (annotation instanceof TimesOfDay) {
TimesOfDay tods = (TimesOfDay) annotation;
for (TimeOfDay tod : tods.value()) {
builder.addBetween(LocalTime.parse(tod.from()),
LocalTime.parse(tod.to()));
}
} else if (annotation instanceof TimeOfDay) {
TimeOfDay tod = (TimeOfDay) annotation;
builder.addBetween(LocalTime.parse(tod.from()),
LocalTime.parse(tod.to()));
}
}
}
}
return builder.build();
}
@Override
public void validate(TimeConfig config, Errors.Collector collector, ProviderRequest request) {
ZonedDateTime now = request.env().time();
config.validate(this, now, collector);
}
@Override
public Collection> supportedAnnotations() {
return Set.of(TimesOfDay.class, TimeOfDay.class, DaysOfWeek.class);
}
/**
* Constraint for a time of day.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
@Inherited
@AbacAnnotation
@Repeatable(TimesOfDay.class)
public @interface TimeOfDay {
/**
* Time after which this resource is accessible within a day.
*
* @return String formatted as "HH:mm:ss.SSS" - hours, minutes, seconds and milliseconds, or "HH:mm:ss", or "HH:mm"
*/
String from() default "00:00:00";
/**
* Time before which this resource is accessible within a day.
*
* @return String formatted as "HH:mm:ss.SSS" - hours, minutes, seconds and milliseconds, or "HH:mm:ss", or "HH:mm"
*/
String to() default "23:59:59.999";
}
/**
* Constraint for a time of day - container for repeating {@link TimeOfDay}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
@Inherited
@AbacAnnotation
public @interface TimesOfDay {
/**
* Repeatable annotation holder.
*
* @return repeatable annotation
*/
TimeOfDay[] value();
}
/**
* Attribute annotation that can limit the days of week the resource is accessible.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
@Inherited
@AbacAnnotation
public @interface DaysOfWeek {
/**
* Return days of week this resource should be accessible on.
*
* @return array of {@link DayOfWeek}
*/
DayOfWeek[] value();
}
/**
* Configuration for time attribute validator.
*/
public static final class TimeConfig implements AbacValidatorConfig {
private final List betweenTimes = new ArrayList<>();
private final Set daysOfWeek = EnumSet.noneOf(DayOfWeek.class);
private TimeConfig(Builder builder) {
this.betweenTimes.addAll(builder.betweenTimes);
this.daysOfWeek.addAll(builder.daysOfWeek);
}
/**
* Builder for this class.
*
* @return a new builder instance
*/
public static Builder builder() {
return new Builder();
}
/**
* Create a time config for a check between times within a day.
*
* @param from from time (ignoring date part)
* @param to to time (ignoring date part)
* @return time configuration for this between config
*/
public static TimeConfig between(LocalTime from, LocalTime to) {
return builder()
.addBetween(from, to)
.build();
}
/**
* Create a time config for a check for days of week.
*
* @param days days the endpoint should be accessible on
* @return time configuration for this week day config
*/
public static TimeConfig daysOfWeek(DayOfWeek... days) {
return builder()
.addDaysOfWeek(days)
.build();
}
/**
* Create an time config from configuration.
*
* @param config configuration located on this validator config key
* @return time configuration based on the config
*/
public static TimeConfig create(Config config) {
Builder builder = TimeConfig.builder();
config.get("time-of-day").asList(Config.class)
.get()
.forEach(tod -> builder.addBetween(LocalTime.parse(tod.get("from").asString().orElse("00:00:00")),
LocalTime.parse(tod.get("to").asString().orElse("24:00:00"))));
config.get("days-of-week").asList(DayOfWeek.class)
.ifPresent(builder::addDaysOfWeek);
return builder.build();
}
void validate(TimeValidator validator, ZonedDateTime now, Errors.Collector collector) {
// between times - it must fit at least one
boolean valid = false;
LocalTime nowTime = now.toLocalTime();
for (BetweenTime betweenTime : betweenTimes) {
if (betweenTime.isValid(nowTime)) {
valid = true;
}
}
if (!valid) {
collector.fatal(validator, nowTime.format(TIME_FORMATTER) + " is in neither of allowed times: " + betweenTimes);
}
DayOfWeek dayOfWeek = now.getDayOfWeek();
if (!daysOfWeek.contains(dayOfWeek)) {
collector.fatal(validator, dayOfWeek + " is not in allowed days: " + daysOfWeek);
}
}
TimeConfig combineWith(TimeConfig child) {
return TimeConfig.builder().from(this).from(child).build();
}
private static class BetweenTime {
private final LocalTime from;
private final LocalTime to;
BetweenTime(LocalTime from, LocalTime to) {
this.from = from;
this.to = to;
}
boolean isValid(LocalTime nowTime) {
return from.isBefore(nowTime) && to.isAfter(nowTime);
}
@Override
public String toString() {
return from + " - " + to;
}
}
/**
* Fluent API builder for {@link TimeConfig}.
*/
public static final class Builder implements io.helidon.common.Builder {
private final List betweenTimes = new ArrayList<>();
private final Set daysOfWeek = EnumSet.noneOf(DayOfWeek.class);
private Builder() {
}
@Override
public TimeConfig build() {
return new TimeConfig(this);
}
/**
* Add a new "between time" configuration.
*
* @param from from when
* @param to until when
* @return updated builder instance
*/
public Builder addBetween(LocalTime from, LocalTime to) {
this.betweenTimes.add(new BetweenTime(from, to));
return this;
}
/**
* Add a new "day of week" configuration.
*
* @param daysOfWeek days to add
* @return updated builder instance
*/
public Builder addDaysOfWeek(DayOfWeek... daysOfWeek) {
this.daysOfWeek.addAll(Arrays.asList(daysOfWeek));
return this;
}
/**
* Add a new "day of week" configuration.
*
* @param daysOfWeek days to add
* @return updated builder instance
*/
public Builder addDaysOfWeek(List daysOfWeek) {
this.daysOfWeek.addAll(daysOfWeek);
return this;
}
/**
* Update this builder from an existing configuration instance.
*
* @param timeConfig time configuration to add to this builder
* @return updated builder instance
*/
public Builder from(TimeConfig timeConfig) {
betweenTimes.addAll(timeConfig.betweenTimes);
daysOfWeek.addAll(timeConfig.daysOfWeek);
return this;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy