All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openremote.manager.rules.RulesResourceImpl Maven / Gradle / Ivy

/*
 * Copyright 2017, OpenRemote Inc.
 *
 * See the CONTRIBUTORS.txt file in the distribution for a
 * full listing of individual contributors.
 *
 * 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 .
 */
package org.openremote.manager.rules;

import org.openremote.container.timer.TimerService;
import org.openremote.manager.asset.AssetStorageService;
import org.openremote.manager.security.ManagerIdentityService;
import org.openremote.manager.web.ManagerWebResource;
import org.openremote.model.Constants;
import org.openremote.model.asset.Asset;
import org.openremote.model.query.AssetQuery;
import org.openremote.model.asset.UserAssetLink;
import org.openremote.model.http.RequestParams;
import org.openremote.model.query.RulesetQuery;
import org.openremote.model.rules.*;
import org.openremote.model.rules.geofence.GeofenceDefinition;
import org.openremote.model.security.ClientRole;
import org.openremote.model.security.Realm;

import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.logging.Logger;

import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST;
import static jakarta.ws.rs.core.Response.Status.NOT_FOUND;

public class RulesResourceImpl extends ManagerWebResource implements RulesResource {

    private static final Logger LOG = Logger.getLogger(RulesResourceImpl.class.getName());

    final protected RulesetStorageService rulesetStorageService;
    final protected AssetStorageService assetStorageService;
    final protected RulesService rulesService;

    public RulesResourceImpl(TimerService timerService,
                             ManagerIdentityService identityService,
                             RulesetStorageService rulesetStorageService,
                             AssetStorageService assetStorageService,
                             RulesService rulesService) {
        super(timerService, identityService);
        this.rulesetStorageService = rulesetStorageService;
        this.assetStorageService = assetStorageService;
        this.rulesService = rulesService;
    }

    /* ################################################################################################# */

    @Override
    public RulesEngineInfo getGlobalEngineInfo(RequestParams requestParams) {
        if (!isSuperUser()) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }

        return getEngineInfo(rulesService.globalEngine.get());
    }

    @Override
    public RulesEngineInfo getRealmEngineInfo(RequestParams requestParams, String realm) {
        if (!isRealmAccessibleByUser(realm) || isRestrictedUser()) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }

        RulesEngine engine = rulesService.realmEngines.get(realm);
        return getEngineInfo(engine);
    }

    @Override
    public RulesEngineInfo getAssetEngineInfo(RequestParams requestParams, String assetId) {
        Asset asset = assetStorageService.find(assetId, false);

        if (asset == null)
            return null;

        if (!isRealmAccessibleByUser(asset.getRealm())) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }

        if (isRestrictedUser() && !assetStorageService.isUserAsset(getUserId(), assetId)) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }

        RulesEngine engine = rulesService.assetEngines.get(assetId);
        return getEngineInfo(engine);
    }

    protected RulesEngineInfo getEngineInfo(RulesEngine engine) {
        if (engine == null) {
            return null;
        }

        int compilationErrorCount = engine.getCompilationErrorDeploymentCount();
        int executionErrorCount = engine.getExecutionErrorDeploymentCount();

        return new RulesEngineInfo(
            engine.getStatus(),
            compilationErrorCount,
            executionErrorCount
        );
    }

    @Override
    public GlobalRuleset[] getGlobalRulesets(@BeanParam RequestParams requestParams, List languages, boolean fullyPopulate) {
        if (!isSuperUser()) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }

        List result = rulesetStorageService.findAll(GlobalRuleset.class, new RulesetQuery().setLanguages(languages.toArray(new Ruleset.Lang[0])).setFullyPopulate(fullyPopulate));

        // Try and retrieve transient status and error data
        result.forEach(ruleset ->
            rulesService
                .getRulesetDeployment(ruleset.getId())
                .ifPresent(rulesetDeployment -> {
                    ruleset.setStatus(rulesetDeployment.getStatus());
                    ruleset.setError(rulesetDeployment.getErrorMessage());
                })
        );

        return result.toArray(new GlobalRuleset[0]);
    }

    @Override
    public RealmRuleset[] getRealmRulesets(@BeanParam RequestParams requestParams, String realm, List languages, boolean fullyPopulate) {

        if (isAuthenticated() && !isRealmAccessibleByUser(realm)) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }

        boolean publicOnly = !isAuthenticated() || isRestrictedUser() | !hasResourceRole(ClientRole.READ_RULES.getValue(), Constants.KEYCLOAK_CLIENT_ID);

        List result = rulesetStorageService.findAll(
            RealmRuleset.class,
            new RulesetQuery().
                setRealm(realm)
                .setLanguages(languages.toArray(new Ruleset.Lang[0]))
                .setFullyPopulate(fullyPopulate)
                .setPublicOnly(publicOnly));

        // Try and retrieve transient status and error data
        result.forEach(ruleset ->
            rulesService
                .getRulesetDeployment(ruleset.getId())
                .ifPresent(rulesetDeployment -> {
                    ruleset.setStatus(rulesetDeployment.getStatus());
                    ruleset.setError(rulesetDeployment.getErrorMessage());
                })
        );

        return result.toArray(new RealmRuleset[0]);
    }

    @Override
    public AssetRuleset[] getAssetRulesets(@BeanParam RequestParams requestParams, String assetId, List languages, boolean fullyPopulate) {
        Asset asset = assetStorageService.find(assetId, false);
        if (asset == null)
            return new AssetRuleset[0];

        if (isAuthenticated() && !isRealmAccessibleByUser(asset.getRealm())) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }

        boolean publicOnly = !isAuthenticated() || (isRestrictedUser() && !assetStorageService.isUserAsset(getUserId(), assetId)) || !hasResourceRole(ClientRole.READ_RULES.getValue(), Constants.KEYCLOAK_CLIENT_ID);

        List result = rulesetStorageService.findAll(
            AssetRuleset.class,
            new RulesetQuery()
                .setRealm(asset.getRealm())
                .setAssetIds(assetId)
                .setPublicOnly(publicOnly)
                .setLanguages(languages.toArray(new Ruleset.Lang[0]))
                .setFullyPopulate(fullyPopulate));

        // Try and retrieve transient status and error data
        result.forEach(ruleset ->
            rulesService
                .getRulesetDeployment(ruleset.getId())
                .ifPresent(rulesetDeployment -> {
                    ruleset.setStatus(rulesetDeployment.getStatus());
                    ruleset.setError(rulesetDeployment.getErrorMessage());
                })
        );
        return result.toArray(new AssetRuleset[0]);
    }

    /* ################################################################################################# */

    @Override
    public long createGlobalRuleset(@BeanParam RequestParams requestParams, GlobalRuleset ruleset) {
        if (!isSuperUser()) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        ruleset = rulesetStorageService.merge(ruleset);
        return ruleset.getId();
    }

    @Override
    public GlobalRuleset getGlobalRuleset(@BeanParam RequestParams requestParams, Long id) {
        if (!isSuperUser()) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        GlobalRuleset ruleset = rulesetStorageService.find(GlobalRuleset.class, id);
        if (ruleset == null) {
            throw new WebApplicationException(NOT_FOUND);
        }
        return ruleset;
    }

    @Override
    public void updateGlobalRuleset(@BeanParam RequestParams requestParams, Long id, GlobalRuleset ruleset) {
        if (!isSuperUser()) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        GlobalRuleset existingRuleset = rulesetStorageService.find(GlobalRuleset.class, id);
        if (existingRuleset == null)
            throw new WebApplicationException(NOT_FOUND);
        rulesetStorageService.merge(ruleset);
    }

    @Override
    public void deleteGlobalRuleset(@BeanParam RequestParams requestParams, Long id) {
        if (!isSuperUser()) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        rulesetStorageService.delete(GlobalRuleset.class, id);
    }

    /* ################################################################################################# */

    @Override
    public long createRealmRuleset(@BeanParam RequestParams requestParams, RealmRuleset ruleset) {
        Realm realm = identityService.getIdentityProvider().getRealm(ruleset.getRealm());
        if (realm == null) {
            throw new WebApplicationException(BAD_REQUEST);
        }
        if (!isRealmActiveAndAccessible(realm) || isRestrictedUser()) {
            LOG.fine("Realm '" + realm + "' is nonexistent, inactive or inaccessible: username=" + getUsername());
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }

        // Only super users can create/modify groovy rules
        // TODO: Implement robust groovy sandbox
        if (ruleset.getLang() == Ruleset.Lang.GROOVY && !isSuperUser()) {
            throw new ForbiddenException("Only super users can create/modify groovy rules for security reasons");
        }
        ruleset = rulesetStorageService.merge(ruleset);
        return ruleset.getId();
    }

    @Override
    public RealmRuleset getRealmRuleset(@BeanParam RequestParams requestParams, Long id) {
        RealmRuleset ruleset = rulesetStorageService.find(RealmRuleset.class, id);
        if (ruleset == null) {
            throw new WebApplicationException(NOT_FOUND);
        }
        Realm realm = identityService.getIdentityProvider().getRealm(ruleset.getRealm());
        if (realm == null) {
            throw new WebApplicationException(BAD_REQUEST);
        }
        if (!isRealmActiveAndAccessible(realm) || isRestrictedUser()) {
            LOG.fine("Forbidden access for user '" + getUsername() + "': " + realm);
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        return ruleset;
    }

    @Override
    public void updateRealmRuleset(@BeanParam RequestParams requestParams, Long id, RealmRuleset ruleset) {
        RealmRuleset existingRuleset = rulesetStorageService.find(RealmRuleset.class, id);
        if (existingRuleset == null) {
            throw new WebApplicationException(NOT_FOUND);
        }
        Realm realm = identityService.getIdentityProvider().getRealm(existingRuleset.getRealm());
        if (realm == null) {
            throw new WebApplicationException(BAD_REQUEST);
        }
        if (!isRealmActiveAndAccessible(realm) || isRestrictedUser()) {
            LOG.fine("Forbidden access for user '" + getUsername() + "': " + realm);
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        if (!id.equals(ruleset.getId())) {
            throw new WebApplicationException("Requested ID and ruleset ID don't match", BAD_REQUEST);
        }
        if (!existingRuleset.getRealm().equals(ruleset.getRealm())) {
            throw new WebApplicationException("Requested realm and existing ruleset realm must match", BAD_REQUEST);
        }

        // Only super users can create/modify groovy rules
        // TODO: Implement robust groovy sandbox
        if (ruleset.getLang() == Ruleset.Lang.GROOVY && !isSuperUser()) {
            throw new ForbiddenException("Only super users can create/modify groovy rules for security reasons");
        }
        rulesetStorageService.merge(ruleset);
    }

    @Override
    public void deleteRealmRuleset(@BeanParam RequestParams requestParams, Long id) {
        RealmRuleset ruleset = rulesetStorageService.find(RealmRuleset.class, id);
        if (ruleset == null) {
            return;
        }
        Realm realm = identityService.getIdentityProvider().getRealm(ruleset.getRealm());
        if (realm == null) {
            throw new WebApplicationException(BAD_REQUEST);
        }
        if (!isRealmActiveAndAccessible(realm) || isRestrictedUser()) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }

        // Only super users can create/modify groovy rules
        // TODO: Implement robust groovy sandbox
        if (ruleset.getLang() == Ruleset.Lang.GROOVY && !isSuperUser()) {
            throw new ForbiddenException("Only super users can create/modify groovy rules for security reasons");
        }

        rulesetStorageService.delete(RealmRuleset.class, id);
    }

    /* ################################################################################################# */

    @Override
    public long createAssetRuleset(@BeanParam RequestParams requestParams, AssetRuleset ruleset) {
        String assetId = ruleset.getAssetId();
        if (assetId == null || assetId.length() == 0) {
            throw new WebApplicationException("Missing asset identifier value", BAD_REQUEST);
        }
        Asset asset = assetStorageService.find(assetId, false);
        if (asset == null) {
            throw new WebApplicationException(NOT_FOUND);
        }
        if (!isRealmAccessibleByUser(asset.getRealm())) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        if (isRestrictedUser() && !assetStorageService.isUserAsset(getUserId(), asset.getId())) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }

        // Only super users can create/modify groovy rules
        // TODO: Implement robust groovy sandbox
        if (ruleset.getLang() == Ruleset.Lang.GROOVY && !isSuperUser()) {
            throw new ForbiddenException("Only super users can create/modify groovy rules for security reasons");
        }

        ruleset = rulesetStorageService.merge(ruleset);
        return ruleset.getId();
    }

    @Override
    public AssetRuleset getAssetRuleset(@BeanParam RequestParams requestParams, Long id) {
        AssetRuleset ruleset = rulesetStorageService.find(AssetRuleset.class, id);
        if (ruleset == null) {
            throw new WebApplicationException(NOT_FOUND);
        }
        Asset asset = assetStorageService.find(ruleset.getAssetId(), false);
        if (asset == null) {
            throw new WebApplicationException(NOT_FOUND);
        }
        if (!isRealmAccessibleByUser(asset.getRealm())) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        if (isRestrictedUser() && !assetStorageService.isUserAsset(getUserId(), asset.getId())) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        return ruleset;
    }

    @Override
    public void updateAssetRuleset(@BeanParam RequestParams requestParams, Long id, AssetRuleset ruleset) {
        AssetRuleset existingRuleset = rulesetStorageService.find(AssetRuleset.class, id);
        if (existingRuleset == null) {
            throw new WebApplicationException(NOT_FOUND);
        }
        Asset asset = assetStorageService.find(existingRuleset.getAssetId(), false);
        if (asset == null) {
            throw new WebApplicationException(NOT_FOUND);
        }
        if (!isRealmAccessibleByUser(asset.getRealm())) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        if (isRestrictedUser() && !assetStorageService.isUserAsset(getUserId(), asset.getId())) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        if (!id.equals(ruleset.getId())) {
            throw new WebApplicationException("Requested ID and ruleset ID don't match", BAD_REQUEST);
        }
        if (!existingRuleset.getAssetId().equals(ruleset.getAssetId())) {
            throw new WebApplicationException("Can't update asset ID, delete and create the ruleset to reassign",
                                              BAD_REQUEST);
        }

        // Only super users can create/modify groovy rules
        // TODO: Implement robust groovy sandbox
        if (ruleset.getLang() == Ruleset.Lang.GROOVY && !isSuperUser()) {
            throw new ForbiddenException("Only super users can create/modify groovy rules for security reasons");
        }
        rulesetStorageService.merge(ruleset);
    }

    @Override
    public void deleteAssetRuleset(@BeanParam RequestParams requestParams, Long id) {
        AssetRuleset ruleset = rulesetStorageService.find(AssetRuleset.class, id);
        if (ruleset == null) {
            return;
        }
        Asset asset = assetStorageService.find(ruleset.getAssetId(), false);
        if (asset == null) {
            throw new WebApplicationException(NOT_FOUND);
        }
        if (!isRealmAccessibleByUser(asset.getRealm())) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }
        if (isRestrictedUser() && !assetStorageService.isUserAsset(getUserId(), asset.getId())) {
            throw new WebApplicationException(Response.Status.FORBIDDEN);
        }

        // Only super users can create/modify groovy rules
        // TODO: Implement robust groovy sandbox
        if (ruleset.getLang() == Ruleset.Lang.GROOVY && !isSuperUser()) {
            throw new ForbiddenException("Only super users can create/modify groovy rules for security reasons");
        }

        rulesetStorageService.delete(AssetRuleset.class, id);
    }

    @Override
    public GeofenceDefinition[] getAssetGeofences(@BeanParam RequestParams requestParams, String assetId) {
        Asset asset;

        asset = assetStorageService.find(
            new AssetQuery()
                .select(new AssetQuery.Select().excludeAttributes())
                .ids(assetId));

        if (asset == null)
            return new GeofenceDefinition[0];

        // If not linked to users check if asset is marked as public read
        if (!asset.isAccessPublicRead()) {

            // If asset is linked to users then only those users can get the geofences for it
            List userAssetLinks = assetStorageService.findUserAssetLinks(asset.getRealm(), null, assetId);

            if (!userAssetLinks.isEmpty()) {
                if (!isAuthenticated() || userAssetLinks.stream().noneMatch(userAssetLink -> userAssetLink.getId().getUserId().equals(getUserId()))) {
                    throw new WebApplicationException(Response.Status.FORBIDDEN);
                }
            }
        }

        return rulesService.getAssetGeofences(assetId);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy