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

alpine.persistence.AlpineQueryManager Maven / Gradle / Ivy

There is a newer version: 3.1.1
Show newest version
/*
 * This file is part of Alpine.
 *
 * 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.
 *
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (c) Steve Springett. All Rights Reserved.
 */
package alpine.persistence;

import alpine.security.ApiKeyGenerator;
import alpine.event.LdapSyncEvent;
import alpine.event.framework.EventService;
import alpine.event.framework.LoggableSubscriber;
import alpine.event.framework.Subscriber;
import alpine.common.logging.Logger;
import alpine.model.ApiKey;
import alpine.model.ConfigProperty;
import alpine.model.EventServiceLog;
import alpine.model.LdapUser;
import alpine.model.ManagedUser;
import alpine.model.MappedLdapGroup;
import alpine.model.MappedOidcGroup;
import alpine.model.OidcGroup;
import alpine.model.OidcUser;
import alpine.model.Permission;
import alpine.model.Team;
import alpine.model.UserPrincipal;
import alpine.resources.AlpineRequest;
import io.jsonwebtoken.lang.Collections;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;

/**
 * This QueryManager provides a concrete extension of {@link AbstractAlpineQueryManager} by
 * providing methods that operate on the default Alpine models such as ManagedUser and Team.
 *
 * @author Steve Springett
 * @since 1.0.0
 */
public class AlpineQueryManager extends AbstractAlpineQueryManager {

    private static final Logger LOGGER = Logger.getLogger(AlpineQueryManager.class);

    /**
     * Default constructor.
     */
    public AlpineQueryManager() {
        super();
    }

    /**
     * Constructs a new AlpineQueryManager.
     * @param pm a PersistenceManager
     */
    public AlpineQueryManager(final PersistenceManager pm) {
        super(pm);
    }

    /**
     * Constructs a new AlpineQueryManager.
     * @param request an AlpineRequest
     */
    public AlpineQueryManager(final AlpineRequest request) {
        super(request);
    }

    /**
     * Constructs a new AlpineQueryManager.
     * @param pm a PersistenceManager
     * @param request an AlpineRequest
     * @since 1.9.3
     */
    public AlpineQueryManager(final PersistenceManager pm, final AlpineRequest request) {
        super(pm, request);
    }

    /**
     * Returns an API key.
     * @param key the key to return
     * @return an ApiKey
     * @since 1.0.0
     */
    @SuppressWarnings("unchecked")
    public ApiKey getApiKey(final String key) {
        final Query query = pm.newQuery(ApiKey.class, "key == :key");
        final List result = (List) query.execute(key);
        return Collections.isEmpty(result) ? null : result.get(0);
    }

    /**
     * Regenerates an API key. This method does not create a new ApiKey object,
     * rather it uses the existing ApiKey object and simply creates a new
     * key string.
     * @param apiKey the ApiKey object to regenerate the key of.
     * @return an ApiKey
     * @since 1.0.0
     */
    public ApiKey regenerateApiKey(final ApiKey apiKey) {
        pm.currentTransaction().begin();
        apiKey.setKey(ApiKeyGenerator.generate());
        pm.currentTransaction().commit();
        return pm.getObjectById(ApiKey.class, apiKey.getId());
    }

    /**
     * Creates a new ApiKey object, including a cryptographically secure
     * API key string.
     * @param team The team to create the key for
     * @return an ApiKey
     */
    public ApiKey createApiKey(final Team team) {
        final List teams = new ArrayList<>();
        teams.add(team);
        pm.currentTransaction().begin();
        final ApiKey apiKey = new ApiKey();
        apiKey.setKey(ApiKeyGenerator.generate());
        apiKey.setCreated(new Date());
        apiKey.setTeams(teams);
        pm.makePersistent(apiKey);
        pm.currentTransaction().commit();
        return pm.getObjectById(ApiKey.class, apiKey.getId());
    }

    public ApiKey updateApiKey(final ApiKey transientApiKey) {
        pm.currentTransaction().begin();
        final ApiKey apiKey = getObjectById(ApiKey.class, transientApiKey.getId());
        apiKey.setComment(transientApiKey.getComment());
        pm.currentTransaction().commit();
        return pm.getObjectById(ApiKey.class, transientApiKey.getId());
    }

    /**
     * Creates a new OidcUser object with the specified username.
     * @param username The username of the new OidcUser. This must reference an
     *                 existing username in the OpenID Connect identity provider.
     * @return an LdapUser
     * @since 1.8.0
     */
    public OidcUser createOidcUser(final String username) {
        pm.currentTransaction().begin();
        final OidcUser user = new OidcUser();
        user.setUsername(username);
        // Subject identifier and email will be synced when a
        // user with the given username signs in for the first time
        pm.makePersistent(user);
        pm.currentTransaction().commit();
        return getObjectById(OidcUser.class, user.getId());
    }

    /**
     * Updates the specified OidcUser.
     * @param transientUser the optionally detached OidcUser object to update.
     * @return an OidcUser
     * @since 1.8.0
     */
    public OidcUser updateOidcUser(final OidcUser transientUser) {
        final OidcUser user = getObjectById(OidcUser.class, transientUser.getId());
        pm.currentTransaction().begin();
        user.setSubjectIdentifier(transientUser.getSubjectIdentifier());
        user.setEmail(transientUser.getEmail());
        pm.currentTransaction().commit();
        return pm.getObjectById(OidcUser.class, user.getId());
    }

    /**
     * Retrieves an OidcUser containing the specified username. If the username
     * does not exist, returns null.
     * @param username The username to retrieve
     * @return an OidcUser
     * @since 1.8.0
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public OidcUser getOidcUser(final String username) {
        final Query query = pm.newQuery(OidcUser.class, "username == :username");
        final List result = (List) query.execute(username);
        return Collections.isEmpty(result) ? null : result.get(0);
    }

    /**
     * Returns a complete list of all OidcUser objects, in ascending order by username.
     * @return a list of OidcUser
     * @since 1.8.0
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public List getOidcUsers() {
        final Query query = pm.newQuery(OidcUser.class);
        query.setOrdering("username asc");
        return (List) query.execute();
    }

    /**
     * Creates a OidcGroup.
     * @param name Name of the group to create
     * @return a OidcGroup
     * @since 1.8.0
     */
    public OidcGroup createOidcGroup(final String name) {
        pm.currentTransaction().begin();
        final OidcGroup group = new OidcGroup();
        group.setName(name);
        pm.makePersistent(group);
        pm.currentTransaction().commit();
        return getObjectByUuid(OidcGroup.class, group.getUuid());
    }

    /**
     * Updates a OidcGroup.
     * @param oidcGroup The group to update
     * @return a refreshed OidcGroup
     * @since 1.8.0
     */
    public OidcGroup updateOidcGroup(final OidcGroup oidcGroup) {
        final OidcGroup oidcGroupToUpdate = getObjectByUuid(OidcGroup.class, oidcGroup.getUuid());
        pm.currentTransaction().begin();
        oidcGroupToUpdate.setName(oidcGroup.getName());
        pm.currentTransaction().commit();
        return pm.getObjectById(OidcGroup.class, oidcGroupToUpdate.getId());
    }

    /**
     * Returns a complete list of all OidcGroup objects, in ascending order by name.
     * @return a list of OidcGroup
     * @since 1.8.0
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public List getOidcGroups() {
        final Query query = pm.newQuery(OidcGroup.class);
        query.setOrdering("name asc");
        return (List) query.execute();
    }

    /**
     * Returns an OidcGroup containing the specified name. If the name
     * does not exist, returns null.
     * @param name Name of the group to retrieve
     * @return an OidcGroup
     * @since 1.8.0
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public OidcGroup getOidcGroup(final String name) {
        final Query query = pm.newQuery(OidcGroup.class, "name == :name");
        final List result = (List) query.execute(name);
        return Collections.isEmpty(result) ? null : result.get(0);
    }

    /**
     * This method dynamically assigns team membership to the specified user from
     * the list of OpenID Connect groups the user is a member of. The method will look
     * up any {@link MappedOidcGroup}s and ensure the user is only a member of the
     * teams that have a mapping to an OpenID Connect group for which the user is a member.
     * @param user the OpenID Connect user to sync team membership for
     * @param groupNames a list of OpenID Connect groups the user is a member of
     * @return a refreshed OidcUser object
     * @since 1.8.0
     */
    public OidcUser synchronizeTeamMembership(final OidcUser user, final List groupNames) {
        LOGGER.debug("Synchronizing team membership for OpenID Connect user " + user.getUsername());
        final List removeThese = new ArrayList<>();

        if (user.getTeams() != null) {
            for (final Team team : user.getTeams()) {
                LOGGER.debug(user.getUsername() + " is a member of team: " + team.getName());
                if (team.getMappedOidcGroups() != null && !team.getMappedOidcGroups().isEmpty()) {
                    for (final MappedOidcGroup mappedOidcGroup : team.getMappedOidcGroups()) {
                        LOGGER.debug(mappedOidcGroup.getGroup().getName() + " is mapped to team: " + team.getName());
                        if (!groupNames.contains(mappedOidcGroup.getGroup().getName())) {
                            LOGGER.debug(mappedOidcGroup.getGroup().getName() + " is not identified in the List of groups specified. Queuing removal of membership for user " + user.getUsername());
                            removeThese.add(team);
                        }
                    }
                } else {
                    LOGGER.debug(team.getName() + " does not have any mapped OpenID Connect groups. Queuing removal of " + user.getUsername() + " from team: " + team.getName());
                    removeThese.add(team);
                }
            }
        }

        for (final Team team : removeThese) {
            LOGGER.debug("Removing user: " + user.getUsername() + " from team: " + team.getName());
            removeUserFromTeam(user, team);
        }

        for (final String groupName : groupNames) {
            final OidcGroup group = getOidcGroup(groupName);
            if (group == null) {
                LOGGER.debug("Unknown OpenID Connect group " + groupName);
                continue;
            }

            for (final MappedOidcGroup mappedOidcGroup : getMappedOidcGroups(group)) {
                LOGGER.debug("Adding user: " + user.getUsername() + " to team: " + mappedOidcGroup.getTeam().getName());
                addUserToTeam(user, mappedOidcGroup.getTeam());
            }
        }

        return getObjectById(OidcUser.class, user.getId());
    }

    /**
     * This method adds the specified user to teams with the specified names. It does not
     * remove the user from any teams, and silently ignores references to teams that do not exist.
     * @param user the OpenID Connect user to sync team membership for
     * @param teamNames a list of teams the user is a member of
     * @return a refreshed OidcUser object
     * @since 2.2.5
     */
    public OidcUser addUserToTeams(final OidcUser user, final List teamNames) {
        LOGGER.debug("Synchronizing team membership for OpenID Connect user " + user.getUsername());

        for (final String teamName : teamNames) {
            Team team = getTeam(teamName);
            if (team == null) {
                LOGGER.warn("Cannot add user " + user.getUsername() + " to team " + teamName + ", because no team with that name exists");
            } else {
                LOGGER.debug("Adding user: " + user.getUsername() + " to team: " + teamName);
                addUserToTeam(user, team);
            }
        }

        return getObjectById(OidcUser.class, user.getId());
    }

    /**
     * Retrieves an LdapUser containing the specified username. If the username
     * does not exist, returns null.
     * @param username The username to retrieve
     * @return an LdapUser
     * @since 1.0.0
     */
    @SuppressWarnings("unchecked")
    public LdapUser getLdapUser(final String username) {
        final Query query = pm.newQuery(LdapUser.class, "username == :username");
        final List result = (List) query.execute(username);
        return Collections.isEmpty(result) ? null : result.get(0);
    }

    /**
     * Returns a complete list of all LdapUser objects, in ascending order by username.
     * @return a list of LdapUsers
     * @since 1.0.0
     */
    @SuppressWarnings("unchecked")
    public List getLdapUsers() {
        final Query query = pm.newQuery(LdapUser.class);
        query.setOrdering("username asc");
        return (List) query.execute();
    }

    /**
     * Creates a new LdapUser object with the specified username.
     * @param username The username of the new LdapUser. This must reference an existing username in the directory service
     * @return an LdapUser
     * @since 1.0.0
     */
    public LdapUser createLdapUser(final String username) {
        pm.currentTransaction().begin();
        final LdapUser user = new LdapUser();
        user.setUsername(username);
        user.setDN("Syncing...");
        pm.makePersistent(user);
        pm.currentTransaction().commit();
        EventService.getInstance().publish(new LdapSyncEvent(user.getUsername()));
        return getObjectById(LdapUser.class, user.getId());
    }

    /**
     * Updates the specified LdapUser.
     * @param transientUser the optionally detached LdapUser object to update.
     * @return an LdapUser
     * @since 1.0.0
     */
    public LdapUser updateLdapUser(final LdapUser transientUser) {
        final LdapUser user = getObjectById(LdapUser.class, transientUser.getId());
        pm.currentTransaction().begin();
        user.setDN(transientUser.getDN());
        pm.currentTransaction().commit();
        return pm.getObjectById(LdapUser.class, user.getId());
    }

    /**
     * This method dynamically assigns team membership to the specified user from
     * the list of LDAP group DN's the user is a member of. The method will look
     * up any {@link MappedLdapGroup}s and ensure the user is only a member of the
     * teams that have a mapping to an LDAP group for which the user is a member.
     * @param user the LDAP user to sync team membership for
     * @param groupDNs a list of LDAP group DNs the user is a member of
     * @return a refreshed LdapUser object
     * @since 1.4.0
     */
    public LdapUser synchronizeTeamMembership(final LdapUser user, final List groupDNs) {
        LOGGER.debug("Synchronizing team membership for " + user.getUsername());
        final List removeThese = new ArrayList<>();
        if (user.getTeams() != null) {
            for (final Team team : user.getTeams()) {
                LOGGER.debug(user.getUsername() + " is a member of team: " + team.getName());
                if (team.getMappedLdapGroups() != null) {
                    for (final MappedLdapGroup mappedLdapGroup : team.getMappedLdapGroups()) {
                        LOGGER.debug(mappedLdapGroup.getDn() + " is mapped to team: " + team.getName());
                        if (!groupDNs.contains(mappedLdapGroup.getDn())) {
                            LOGGER.debug(mappedLdapGroup.getDn() + " is not identified in the List of group DNs specified. Queuing removal of membership for user " + user.getUsername());
                            removeThese.add(team);
                        }
                    }
                } else {
                    LOGGER.debug(team.getName() + " does not have any mapped LDAP groups. Queuing removal of " + user.getUsername() + " from team: " + team.getName());
                    removeThese.add(team);
                }
            }
        }
        for (final Team team: removeThese) {
            LOGGER.debug("Removing user: " + user.getUsername() + " from team: " + team.getName());
            removeUserFromTeam(user, team);
        }
        for (final String groupDN: groupDNs) {
            for (final MappedLdapGroup mappedLdapGroup: getMappedLdapGroups(groupDN)) {
                LOGGER.debug("Adding user: " + user.getUsername() + " to team: " + mappedLdapGroup.getTeam());
                addUserToTeam(user, mappedLdapGroup.getTeam());
            }
        }
        return getObjectById(LdapUser.class, user.getId());
    }

    /**
     * Creates a new ManagedUser object.
     * @param username The username for the user
     * @param passwordHash The hashed password.
     * @return a ManagedUser
     * @since 1.0.0
     */
    public ManagedUser createManagedUser(final String username, final String passwordHash) {
        return createManagedUser(username, null, null, passwordHash, false, false, false);
    }

    /**
     * Creates a new ManagedUser object.
     * @param username The username for the user
     * @param fullname The fullname of the user
     * @param email The users email address
     * @param passwordHash The hashed password
     * @param forcePasswordChange Whether or not user needs to change password on next login or not
     * @param nonExpiryPassword Whether or not the users password ever expires or not
     * @param suspended Whether or not user being created is suspended or not
     * @return a ManagedUser
     * @since 1.1.0
     */
    public ManagedUser createManagedUser(final String username, final String fullname, final String email,
                                         final String passwordHash, final boolean forcePasswordChange,
                                         final boolean nonExpiryPassword, final boolean suspended) {
        pm.currentTransaction().begin();
        final ManagedUser user = new ManagedUser();
        user.setUsername(username);
        user.setFullname(fullname);
        user.setEmail(email);
        user.setPassword(passwordHash);
        user.setForcePasswordChange(forcePasswordChange);
        user.setNonExpiryPassword(nonExpiryPassword);
        user.setSuspended(suspended);
        user.setLastPasswordChange(new Date());
        pm.makePersistent(user);
        pm.currentTransaction().commit();
        return getObjectById(ManagedUser.class, user.getId());
    }

    /**
     * Updates the specified ManagedUser.
     * @param transientUser the optionally detached ManagedUser object to update.
     * @return an ManagedUser
     * @since 1.0.0
     */
    public ManagedUser updateManagedUser(final ManagedUser transientUser) {
        final ManagedUser user = getObjectById(ManagedUser.class, transientUser.getId());
        pm.currentTransaction().begin();
        user.setFullname(transientUser.getFullname());
        user.setEmail(transientUser.getEmail());
        user.setForcePasswordChange(transientUser.isForcePasswordChange());
        user.setNonExpiryPassword(transientUser.isNonExpiryPassword());
        user.setSuspended(transientUser.isSuspended());
        if (transientUser.getPassword() != null) {
            if (!user.getPassword().equals(transientUser.getPassword())) {
                user.setLastPasswordChange(new Date());
            }
            user.setPassword(transientUser.getPassword());
        }
        pm.currentTransaction().commit();
        return pm.getObjectById(ManagedUser.class, user.getId());
    }

    /**
     * Returns a ManagedUser with the specified username. If the username
     * does not exist, returns null.
     * @param username The username to retrieve
     * @return a ManagedUser
     * @since 1.0.0
     */
    @SuppressWarnings("unchecked")
    public ManagedUser getManagedUser(final String username) {
        final Query query = pm.newQuery(ManagedUser.class, "username == :username");
        final List result = (List) query.execute(username);
        return Collections.isEmpty(result) ? null : result.get(0);
    }

    /**
     * Returns a complete list of all ManagedUser objects, in ascending order by username.
     * @return a List of ManagedUsers
     * @since 1.0.0
     */
    @SuppressWarnings("unchecked")
    public List getManagedUsers() {
        final Query query = pm.newQuery(ManagedUser.class);
        query.setOrdering("username asc");
        return (List) query.execute();
    }

    /**
     * Resolves a UserPrincipal. Default order resolution is to first match
     * on ManagedUser then on LdapUser and finally on OidcUser. This may be
     * configurable in a future release.
     * @param username the username of the principal to retrieve
     * @return a UserPrincipal if found, null if not found
     * @since 1.0.0
     */
    public UserPrincipal getUserPrincipal(String username) {
        UserPrincipal principal = getManagedUser(username);
        if (principal != null) {
            return principal;
        }
        principal = getLdapUser(username);
        if (principal != null) {
            return principal;
        }
        return getOidcUser(username);
    }

    /**
     * Creates a new Team with the specified name. If createApiKey is true,
     * then {@link #createApiKey} is invoked and a cryptographically secure
     * API key is generated.
     * @param name The name of th team
     * @param createApiKey whether or not to create an API key for the team
     * @return a Team
     * @since 1.0.0
     */
    public Team createTeam(final String name, final boolean createApiKey) {
        pm.currentTransaction().begin();
        final Team team = new Team();
        team.setName(name);
        //todo assign permissions
        pm.makePersistent(team);
        pm.currentTransaction().commit();
        if (createApiKey) {
            createApiKey(team);
        }
        return getObjectByUuid(Team.class, team.getUuid(), Team.FetchGroup.ALL.name());
    }

    /**
     * Returns a complete list of all Team objects, in ascending order by name.
     * @return a List of Teams
     * @since 1.0.0
     */
    @SuppressWarnings("unchecked")
    public List getTeams() {
        pm.getFetchPlan().addGroup(Team.FetchGroup.ALL.name());
        final Query query = pm.newQuery(Team.class);
        query.setOrdering("name asc");
        return (List) query.execute();
    }

    /**
     * Returns a Team containing the specified name. If the name
     * does not exist, returns null.
     * @param name Name of the team to retrieve
     * @return a Team
     * @since 2.2.5
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Team getTeam(final String name) {
        final Query query = pm.newQuery(Team.class, "name == :name");
        final List result = (List) query.execute(name);
        return Collections.isEmpty(result) ? null : result.get(0);
    }

    /**
     * Updates the specified Team.
     * @param transientTeam the optionally detached Team object to update
     * @return a Team
     * @since 1.0.0
     */
    public Team updateTeam(final Team transientTeam) {
        final Team team = getObjectByUuid(Team.class, transientTeam.getUuid());
        pm.currentTransaction().begin();
        team.setName(transientTeam.getName());
        //todo assign permissions
        pm.currentTransaction().commit();
        return pm.getObjectById(Team.class, team.getId());
    }

    /**
     * Associates a UserPrincipal to a Team.
     * @param user The user to bind
     * @param team The team to bind
     * @return true if operation was successful, false if not. This is not an indication of team association,
     * an unsuccessful return value may be due to the team or user not existing, or a binding that already
     * exists between the two.
     * @since 1.0.0
     */
    public boolean addUserToTeam(final UserPrincipal user, final Team team) {
        List teams = user.getTeams();
        boolean found = false;
        if (teams == null) {
            teams = new ArrayList<>();
        }
        for (final Team t: teams) {
            if (team.getUuid().equals(t.getUuid())) {
                found = true;
            }
        }
        if (!found) {
            pm.currentTransaction().begin();
            teams.add(team);
            user.setTeams(teams);
            pm.currentTransaction().commit();
            return true;
        }
        return false;
    }

    /**
     * Removes the association of a UserPrincipal to a Team.
     * @param user The user to unbind
     * @param team The team to unbind
     * @return true if operation was successful, false if not. This is not an indication of team disassociation,
     * an unsuccessful return value may be due to the team or user not existing, or a binding that may not exist.
     * @since 1.0.0
     */
    public boolean removeUserFromTeam(final UserPrincipal user, final Team team) {
        final List teams = user.getTeams();
        if (teams == null) {
            return false;
        }
        boolean found = false;
        for (final Team t: teams) {
            if (team.getUuid().equals(t.getUuid())) {
                found = true;
            }
        }
        if (found) {
            pm.currentTransaction().begin();
            teams.remove(team);
            user.setTeams(teams);
            pm.currentTransaction().commit();
            return true;
        }
        return false;
    }

    /**
     * Creates a Permission object.
     * @param name The name of the permission
     * @param description the permissions description
     * @return a Permission
     * @since 1.1.0
     */
    public Permission createPermission(final String name, final String description) {
        pm.currentTransaction().begin();
        final Permission permission = new Permission();
        permission.setName(name);
        permission.setDescription(description);
        pm.makePersistent(permission);
        pm.currentTransaction().commit();
        return getObjectById(Permission.class, permission.getId());
    }

    /**
     * Retrieves a Permission by its name.
     * @param name The name of the permission
     * @return a Permission
     * @since 1.1.0
     */
    @SuppressWarnings("unchecked")
    public Permission getPermission(final String name) {
        final Query query = pm.newQuery(Permission.class, "name == :name");
        final List result = (List) query.execute(name);
        return Collections.isEmpty(result) ? null : result.get(0);
    }

    /**
     * Returns a list of all Permissions defined in the system.
     * @return a List of Permission objects
     * @since 1.1.0
     */
    @SuppressWarnings("unchecked")
    public List getPermissions() {
        final Query query = pm.newQuery(Permission.class);
        query.setOrdering("name asc");
        return (List) query.execute();
    }

    /**
     * Determines the effective permissions for the specified user by collecting
     * a List of all permissions assigned to the user either directly, or through
     * team membership.
     * @param user the user to retrieve permissions for
     * @return a List of Permission objects
     * @since 1.1.0
     */
    public List getEffectivePermissions(UserPrincipal user) {
        final LinkedHashSet permissions = new LinkedHashSet<>();
        if (user.getPermissions() != null) {
            permissions.addAll(user.getPermissions());
        }
        if (user.getTeams() != null) {
            for (final Team team: user.getTeams()) {
                final List teamPermissions = getObjectById(Team.class, team.getId()).getPermissions();
                if (teamPermissions != null) {
                    permissions.addAll(teamPermissions);
                }
            }
        }
        return new ArrayList<>(permissions);
    }

    /**
     * Determines if the specified UserPrincipal has been assigned the specified permission.
     * @param user the UserPrincipal to query
     * @param permissionName the name of the permission
     * @return true if the user has the permission assigned, false if not
     * @since 1.0.0
     */
    public boolean hasPermission(final UserPrincipal user, String permissionName) {
        return hasPermission(user, permissionName, false);
    }

    /**
     * Determines if the specified UserPrincipal has been assigned the specified permission.
     * @param user the UserPrincipal to query
     * @param permissionName the name of the permission
     * @param includeTeams if true, will query all Team membership assigned to the user for the specified permission
     * @return true if the user has the permission assigned, false if not
     * @since 1.0.0
     */
    public boolean hasPermission(final UserPrincipal user, String permissionName, boolean includeTeams) {
        Query query;
        if (user instanceof final ManagedUser managedUser) {
            query = pm.newQuery(Permission.class, "name == :permissionName && managedUsers.contains(user) && user.id == :userId");
            query.declareVariables("alpine.model.ManagedUser user");
            query.setParameters(permissionName, managedUser.getId());
        } else if (user instanceof final LdapUser ldapUser) {
            query = pm.newQuery(Permission.class, "name == :permissionName && ldapUsers.contains(user) && user.id == :userId");
            query.declareVariables("alpine.model.LdapUser user");
            query.setParameters(permissionName, ldapUser.getId());
        } else if (user instanceof final OidcUser oidcUser) {
            query = pm.newQuery(Permission.class, "name == :permissionName && oidcUsers.contains(user) && user.id == :userId");
            query.declareVariables("alpine.model.OidcUser user");
            query.setParameters(permissionName, oidcUser.getId());
        } else {
            LOGGER.warn("Unrecognized principal class %s; Unable to verify permissions".formatted(user.getClass()));
            return false;
        }
        query.setResult("count(id)");
        final long count = query.executeResultUnique(Long.class);
        if (count > 0) {
            return true;
        }
        if (includeTeams) {
            for (final Team team: user.getTeams()) {
                if (hasPermission(team, permissionName)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Determines if the specified Team has been assigned the specified permission.
     * @param team the Team to query
     * @param permissionName the name of the permission
     * @return true if the team has the permission assigned, false if not
     * @since 1.0.0
     */
    public boolean hasPermission(final Team team, String permissionName) {
        final Query query = pm.newQuery(Permission.class, "name == :permissionName && teams.contains(team) && team.id == :teamId");
        query.declareVariables("alpine.model.Team team");
        query.setParameters(permissionName, team.getId());
        query.setResult("count(id)");
        return query.executeResultUnique(Long.class) > 0;
    }

    /**
     * Determines if the specified ApiKey has been assigned the specified permission.
     * @param apiKey the ApiKey to query
     * @param permissionName the name of the permission
     * @return true if the apiKey has the permission assigned, false if not
     * @since 1.1.1
     */
    public boolean hasPermission(final ApiKey apiKey, String permissionName) {
        if (apiKey.getTeams() == null) {
            return false;
        }
        for (final Team team: apiKey.getTeams()) {
            final List teamPermissions = getObjectById(Team.class, team.getId()).getPermissions();
            for (final Permission permission: teamPermissions) {
                if (permission.getName().equals(permissionName)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Retrieves a MappedLdapGroup object for the specified Team and LDAP group.
     * @param team a Team object
     * @param dn a String representation of Distinguished Name
     * @return a MappedLdapGroup if found, or null if no mapping exists
     * @since 1.4.0
     */
    @SuppressWarnings("unchecked")
    public MappedLdapGroup getMappedLdapGroup(final Team team, final String dn) {
        final Query query = pm.newQuery(MappedLdapGroup.class, "team == :team && dn == :dn");
        final List result = (List) query.execute(team, dn);
        return Collections.isEmpty(result) ? null : result.get(0);
    }

    /**
     * Retrieves a List of MappedLdapGroup objects for the specified Team.
     * @param team a Team object
     * @return a List of MappedLdapGroup objects
     * @since 1.4.0
     */
    @SuppressWarnings("unchecked")
    public List getMappedLdapGroups(final Team team) {
        final Query query = pm.newQuery(MappedLdapGroup.class, "team == :team");
        return (List) query.execute(team);
    }

    /**
     * Retrieves a List of MappedLdapGroup objects for the specified DN.
     * @param dn a String representation of Distinguished Name
     * @return a List of MappedLdapGroup objects
     * @since 1.4.0
     */
    @SuppressWarnings("unchecked")
    public List getMappedLdapGroups(final String dn) {
        final Query query = pm.newQuery(MappedLdapGroup.class, "dn == :dn");
        return (List) query.execute(dn);
    }

    /**
     * Determines if the specified Team is mapped to the specified LDAP group.
     * @param team a Team object
     * @param dn a String representation of Distinguished Name
     * @return true if a mapping exists, false if not
     * @since 1.4.0
     */
    public boolean isMapped(final Team team, final String dn) {
        return getMappedLdapGroup(team, dn) != null;
    }

    /**
     * Creates a MappedLdapGroup object.
     * @param team The team to map
     * @param dn the distinguished name of the LDAP group to map
     * @return a MappedLdapGroup
     * @since 1.4.0
     */
    public MappedLdapGroup createMappedLdapGroup(final Team team, final String dn) {
        pm.currentTransaction().begin();
        final MappedLdapGroup mapping = new MappedLdapGroup();
        mapping.setTeam(team);
        mapping.setDn(dn);
        pm.makePersistent(mapping);
        pm.currentTransaction().commit();
        return getObjectById(MappedLdapGroup.class, mapping.getId());
    }

    /**
     * Creates a MappedOidcGroup object.
     * @param team The team to map
     * @param group The OIDC group to map
     * @return a MappedOidcGroup
     * @since 1.8.0
     */
    public MappedOidcGroup createMappedOidcGroup(final Team team, final OidcGroup group) {
        pm.currentTransaction().begin();
        final MappedOidcGroup mapping = new MappedOidcGroup();
        mapping.setTeam(team);
        mapping.setGroup(group);
        pm.makePersistent(mapping);
        pm.currentTransaction().commit();
        return getObjectById(MappedOidcGroup.class, mapping.getId());
    }

    /**
     * Retrieves a MappedOidcGroup object for the specified Team and OIDC group.
     * @param team a Team object
     * @param group a OidcGroup object
     * @return a MappedOidcGroup if found, or null if no mapping exists
     * @since 1.8.0
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public MappedOidcGroup getMappedOidcGroup(final Team team, final OidcGroup group) {
        final Query query = pm.newQuery(MappedOidcGroup.class, "team == :team && group == :group");
        final List result = (List) query.execute(team, group);
        return Collections.isEmpty(result) ? null : result.get(0);
    }

    /**
     * Retrieves a List of MappedOidcGroup objects for the specified Team.
     * @param team The team to retrieve mappings for
     * @return a List of MappedOidcGroup objects
     * @since 1.8.0
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public List getMappedOidcGroups(final Team team) {
        final Query query = pm.newQuery(MappedOidcGroup.class, "team == :team");
        return (List) query.execute(team);
    }

    /**
     * Retrieves a List of MappedOidcGroup objects for the specified group.
     * @param group The group to retrieve mappings for
     * @return a List of MappedOidcGroup objects
     * @since 1.8.0
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public List getMappedOidcGroups(final OidcGroup group) {
        final Query query = pm.newQuery(MappedOidcGroup.class, "group == :group");
        return (List) query.execute(group);
    }

    /**
     * Determines if the specified Team is mapped to the specified OpenID Connect group.
     * @param team a Team object
     * @param group a OidcGroup object
     * @return true if a mapping exists, false if not
     * @since 1.8.0
     */
    public boolean isOidcGroupMapped(final Team team, final OidcGroup group) {
        return getMappedOidcGroup(team, group) != null;
    }

    /**
     * Creates a new EventServiceLog. This method will automatically determine
     * if the subscriber is an implementation of {@link LoggableSubscriber} and
     * if so, will log the event. If not, then nothing will be logged and this
     * method will return null.
     * @param clazz the class of the subscriber task that handles the event
     * @return a new EventServiceLog
     */
    public EventServiceLog createEventServiceLog(Class clazz) {
        if (LoggableSubscriber.class.isAssignableFrom(clazz)) {
            pm.currentTransaction().begin();
            final EventServiceLog log = new EventServiceLog();
            log.setSubscriberClass(clazz.getCanonicalName());
            log.setStarted(new Timestamp(new Date().getTime()));
            pm.makePersistent(log);
            pm.currentTransaction().commit();
            return getObjectById(EventServiceLog.class, log.getId());
        }
        return null;
    }

    /**
     * Updates a EventServiceLog.
     * @param eventServiceLog the EventServiceLog to update
     * @return an updated EventServiceLog
     */
    public EventServiceLog updateEventServiceLog(EventServiceLog eventServiceLog) {
        if (eventServiceLog != null) {
            final EventServiceLog log = getObjectById(EventServiceLog.class, eventServiceLog.getId());
            if (log != null) {
                pm.currentTransaction().begin();
                log.setCompleted(new Timestamp(new Date().getTime()));
                pm.currentTransaction().commit();
                return pm.getObjectById(EventServiceLog.class, log.getId());
            }
        }
        return null;
    }

    /**
     * Returns the most recent log entry for the specified Subscriber.
     * If no log entries are found, this method will return null.
     * @param clazz The LoggableSubscriber class to query on
     * @return a EventServiceLog
     * @since 1.0.0
     */
    @SuppressWarnings("unchecked")
    public EventServiceLog getLatestEventServiceLog(final Class clazz) {
        final Query query = pm.newQuery(EventServiceLog.class, "eventClass == :clazz");
        query.setOrdering("completed desc");
        final List result = (List) query.execute(clazz);
        return Collections.isEmpty(result) ? null : result.get(0);
    }

    /**
     * Returns a ConfigProperty with the specified groupName and propertyName.
     * @param groupName the group name of the config property
     * @param propertyName the name of the property
     * @return a ConfigProperty object
     * @since 1.3.0
     */
    @SuppressWarnings("unchecked")
    public ConfigProperty getConfigProperty(final String groupName, final String propertyName) {
        final Query query = pm.newQuery(ConfigProperty.class, "groupName == :groupName && propertyName == :propertyName");
        final List result = (List) query.execute(groupName, propertyName);
        return Collections.isEmpty(result) ? null : result.get(0);
    }

    /**
     * Returns a list of ConfigProperty objects with the specified groupName.
     * @param groupName the group name of the properties
     * @return a List of ConfigProperty objects
     * @since 1.3.0
     */
    @SuppressWarnings("unchecked")
    public List getConfigProperties(final String groupName) {
        final Query query = pm.newQuery(ConfigProperty.class, "groupName == :groupName");
        query.setOrdering("propertyName asc");
        return (List) query.execute(groupName);
    }

    /**
     * Returns a list of ConfigProperty objects.
     * @return a List of ConfigProperty objects
     * @since 1.3.0
     */
    @SuppressWarnings("unchecked")
    public List getConfigProperties() {
        final Query query = pm.newQuery(ConfigProperty.class);
        query.setOrdering("groupName asc, propertyName asc");
        return (List) query.execute();
    }

    /**
     * Creates a ConfigProperty object.
     * @param groupName the group name of the property
     * @param propertyName the name of the property
     * @param propertyValue the value of the property
     * @param propertyType the type of property
     * @param description a description of the property
     * @return a ConfigProperty object
     * @since 1.3.0
     */
    public ConfigProperty createConfigProperty(final String groupName, final String propertyName,
                                               final String propertyValue, final ConfigProperty.PropertyType propertyType,
                                               final String description) {
        pm.currentTransaction().begin();
        final ConfigProperty configProperty = new ConfigProperty();
        configProperty.setGroupName(groupName);
        configProperty.setPropertyName(propertyName);
        configProperty.setPropertyValue(propertyValue);
        configProperty.setPropertyType(propertyType);
        configProperty.setDescription(description);
        pm.makePersistent(configProperty);
        pm.currentTransaction().commit();
        return getObjectById(ConfigProperty.class, configProperty.getId());
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy