org.eclipse.hono.authorization.impl.InMemoryAuthorizationService.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.hono.authorization.impl.InMemoryAuthorizationService.java

Source

/**
 * Copyright (c) 2016, 2017 Bosch Software Innovations GmbH.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Bosch Software Innovations GmbH - initial creation
 */
package org.eclipse.hono.authorization.impl;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.EnumSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

import org.eclipse.hono.service.authorization.AccessControlList;
import org.eclipse.hono.service.authorization.AclEntry;
import org.eclipse.hono.service.authorization.Permission;
import org.eclipse.hono.util.ResourceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import io.vertx.core.Future;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

/**
 * Implementation of AuthorizationService that holds acl data in memory i.e. no persistent storage.
 */
@Component
@ConfigurationProperties(prefix = "hono.authorization")
public final class InMemoryAuthorizationService extends BaseAuthorizationService {

    static final Resource DEFAULT_PERMISSIONS_RESOURCE = new ClassPathResource("permissions.json");
    private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryAuthorizationService.class);
    // holds mapping resource -> acl
    private static final ConcurrentMap<ResourceIdentifier, AccessControlList> resources = new ConcurrentHashMap<>();
    private Resource permissionsResource = DEFAULT_PERMISSIONS_RESOURCE;

    /**
     * Set the resource that the authorization rules should be loaded from.
     * <p>
     * If not set the default permissions will be loaded from <em>classpath:permissions.json</em>.
     * 
     * @param permissionsResource The resource.
     */
    public void setPermissionsPath(final Resource permissionsResource) {
        this.permissionsResource = Objects.requireNonNull(permissionsResource);
    }

    @Override
    protected void doStart(final Future<Void> startFuture) {
        try {
            loadPermissions();
            startFuture.complete();
        } catch (IOException e) {
            LOGGER.error("cannot load permissions from resource {}", permissionsResource, e);
            startFuture.fail(e);
        }
    }

    @Override
    public boolean hasPermission(final String subject, final ResourceIdentifier resource,
            final Permission permission) {
        requireNonNull(subject, "subject is required");
        requireNonNull(resource, "resources is required");
        requireNonNull(permission, "permission is required");

        return hasPermissionInternal(subject, ResourceIdentifier.from(resource.getEndpoint(), "*", null),
                permission)
                || hasPermissionInternal(subject,
                        ResourceIdentifier.from(resource.getEndpoint(), resource.getTenantId(), null), permission)
                || hasPermissionInternal(subject, resource, permission);
    }

    private boolean hasPermissionInternal(final String subject, final ResourceIdentifier resource,
            final Permission permission) {
        return ofNullable(resources.get(resource)).map(acl -> acl.hasPermission(subject, permission)).orElse(false);
    }

    @Override
    public void addPermission(final String subject, final ResourceIdentifier resource, final Permission first,
            final Permission... rest) {
        requireNonNull(first, "permission is required");
        final EnumSet<Permission> permissions = EnumSet.of(first, rest);
        addPermission(subject, resource, permissions);
    }

    @Override
    public void addPermission(final String subject, final ResourceIdentifier resource,
            final Set<Permission> permissions) {
        requireNonNull(subject, "subject is required");
        requireNonNull(resource, "resource is required");
        requireNonNull(permissions, "permission is required");

        LOGGER.trace("adding permission {} for subject {} on resource {}.", permissions, subject, resource);
        resources.computeIfAbsent(resource, key -> new AccessControlList())
                .setAclEntry(new AclEntry(subject, permissions));
    }

    @Override
    public void removePermission(final String subject, final ResourceIdentifier resource, final Permission first,
            final Permission... rest) {
        requireNonNull(subject, "subject is required");
        requireNonNull(resource, "resource is required");
        requireNonNull(first, "permission is required");

        final EnumSet<Permission> permissions = EnumSet.of(first, rest);
        LOGGER.trace("removing permission {} for subject {} on resource {}.", first, subject, resource);
        resources.computeIfPresent(resource, (key, value) -> {
            ofNullable(value.getAclEntry(subject)).map(AclEntry::getPermissions)
                    .ifPresent(p -> p.removeAll(permissions));
            return value;
        });
    }

    private void loadPermissions() throws IOException {
        if (permissionsResource == null) {
            throw new IllegalStateException("permissions resource is not set");
        }
        if (permissionsResource.isReadable()) {
            LOGGER.info("loading permissions from resource {}", permissionsResource.getURI().toString());
            StringBuilder json = new StringBuilder();
            load(permissionsResource, json);
            parsePermissions(new JsonObject(json.toString()));
        } else {
            throw new FileNotFoundException("permissions resource does not exist");
        }
    }

    private void load(final Resource source, final StringBuilder target) throws IOException {

        char[] buffer = new char[4096];
        int bytesRead = 0;
        try (Reader reader = new InputStreamReader(source.getInputStream(), UTF_8)) {
            while ((bytesRead = reader.read(buffer)) > 0) {
                target.append(buffer, 0, bytesRead);
            }
        }
    }

    private void parsePermissions(final JsonObject permissionsObject) {
        permissionsObject.stream().filter(resources -> resources.getValue() instanceof JsonObject)
                .forEach(resources -> {
                    final ResourceIdentifier resourceIdentifier = getResourceIdentifier(resources);
                    final JsonObject subjects = (JsonObject) resources.getValue();
                    subjects.stream().filter(subject -> subject.getValue() instanceof JsonArray)
                            .forEach(subject -> {
                                final JsonArray permissions = (JsonArray) subject.getValue();
                                addPermission(subject.getKey(), resourceIdentifier, toSet(permissions));
                            });
                });
    }

    private ResourceIdentifier getResourceIdentifier(final Map.Entry<String, Object> resources) {
        if (honoConfig.isSingleTenant()) {
            return ResourceIdentifier.fromStringAssumingDefaultTenant(resources.getKey());
        } else {
            return ResourceIdentifier.fromString(resources.getKey());
        }
    }

    private Set<Permission> toSet(final JsonArray array) {
        return array.stream().filter(element -> element instanceof String).map(element -> (String) element)
                .map(Permission::valueOf).collect(Collectors.<Permission>toSet());
    }
}