/* * RESTHeart - the Web API for MongoDB * Copyright (C) SoftInstigate Srl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.restheart.security.impl; import static com.google.common.collect.Sets.newHashSet; import io.undertow.predicate.Predicate; import static io.undertow.predicate.Predicate.PREDICATE_CONTEXT; import io.undertow.predicate.PredicateParser; import io.undertow.security.idm.Account; import io.undertow.server.HttpServerExchange; import java.io.FileNotFoundException; import java.security.Principal; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.function.Consumer; import java.util.stream.Stream; import org.restheart.handlers.RequestContext; import org.restheart.security.AccessManager; /** * @author Andrea Di Cesare {@literal <andrea@softinstigate.com>} */ public final class SimpleAccessManager extends AbstractSimpleSecurityManager implements AccessManager { private final HashMap<String, Set<Predicate>> acl = new HashMap<>(); /** * @param configuration * @throws java.io.FileNotFoundException */ public SimpleAccessManager(Map<String, Object> configuration) throws FileNotFoundException { init(configuration, "permissions"); } @Override Consumer<? super Map<String, Object>> consumeConfiguration() { return u -> { Object _role = u.get("role"); Object _predicate = u.get("predicate"); if (_role == null || !(_role instanceof String)) { throw new IllegalArgumentException("wrong configuration file format. a permission entry is missing the role"); } String role = (String) _role; if (_predicate == null || !(_predicate instanceof String)) { throw new IllegalArgumentException("wrong configuration file format. a permission entry is missing the predicate"); } Predicate predicate = null; try { predicate = PredicateParser.parse((String) _predicate, this.getClass().getClassLoader()); } catch (Throwable t) { throw new IllegalArgumentException("wrong configuration file format. wrong predicate " + _predicate, t); } aclForRole(role).add(predicate); }; } /** * @param exchange * @param context * @return */ @Override public boolean isAllowed(HttpServerExchange exchange, RequestContext context) { if (noAclDefined()) { return false; } // this fixes undertow bug 377 // https://issues.jboss.org/browse/UNDERTOW-377 if (exchange.getAttachment(PREDICATE_CONTEXT) == null) { exchange.putAttachment(PREDICATE_CONTEXT, new TreeMap<String, Object>()); } return roles(exchange).anyMatch(role -> aclForRole(role).stream().anyMatch(p -> p.resolve(exchange))); } @Override public boolean isAuthenticationRequired(final HttpServerExchange exchange) { if (getAcl() == null) { return true; } Set<Predicate> ps = getAcl().get("$unauthenticated"); return ps == null ? true : !ps.stream().anyMatch(p -> p.resolve(exchange)); } private Stream<String> roles(HttpServerExchange exchange) { return account(exchange).getRoles().stream(); } private boolean noAclDefined() { return getAcl() == null; } private Set<Predicate> aclForRole(String role) { Set<Predicate> predicates = getAcl().get(role); if (predicates == null) { predicates = newHashSet(); getAcl().put(role, predicates); } return predicates; } private Account account(HttpServerExchange exchange) { final Account account = exchange.getSecurityContext().getAuthenticatedAccount(); return isAuthenticated(account) ? account : new NotAuthenticatedAccount(); } private boolean isAuthenticated(Account authenticatedAccount) { return authenticatedAccount != null; } /** * @return the acl */ public HashMap<String, Set<Predicate>> getAcl() { return acl; } private static class NotAuthenticatedAccount implements Account { @Override public Principal getPrincipal() { return null; } @Override public Set<String> getRoles() { return newHashSet("$unauthenticated"); } } }