/* * The MIT License (MIT) * * Copyright (c) 2014 Andreas Alanko, Emil Nilsson, Sony Mobile Communications AB. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.sonymobile.jenkins.plugins.gitlab.gitlabauth; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.sonymobile.gitlab.api.GitLabApiClient; import com.sonymobile.gitlab.exceptions.GitLabApiException; import com.sonymobile.gitlab.exceptions.GroupNotFoundException; import com.sonymobile.gitlab.exceptions.UserNotFoundException; import com.sonymobile.gitlab.model.GitLabAccessLevel; import com.sonymobile.gitlab.model.GitLabGroupInfo; import com.sonymobile.gitlab.model.GitLabGroupMemberInfo; import com.sonymobile.gitlab.model.GitLabUserInfo; import com.sonymobile.jenkins.plugins.gitlab.gitlabapi.GitLabConfiguration; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkArgument; /** * An interface to a GitLab server. * * @author Emil Nilsson */ public class GitLab { /** The singleton implementation instance. */ private static Implementation instance = new Implementation(); private GitLab() { /* no public constructor */ } /** * Gets a user. * * @param userId ID of the user * @return a user for the ID or null if the user doesn't exist * @throws GitLabApiException if the connection against GitLab failed */ public static GitLabUserInfo getUser(int userId) throws GitLabApiException { return instance.getUser(userId); } /** * Gets information about a member in a group. * * @param userId ID of the user * @param groupId ID of the group * @return group membership information or null user or group doesn't exist or user isn't member of group * @throws GitLabApiException if the connection against GitLab failed */ public static GitLabGroupMemberInfo getGroupMember(int userId, int groupId) throws GitLabApiException { return instance.getGroupMember(userId, groupId); } /** * Gets information about a member in a group. * * @param userId ID of the user * @param groupPath the group path * @return group membership information or null user or group doesn't exist or user isn't member of group * @throws GitLabApiException if the connection against GitLab failed */ public static GitLabGroupMemberInfo getGroupMember(int userId, String groupPath) throws GitLabApiException { GitLabGroupInfo group = getGroupByPath(groupPath); if (group == null) { // group doesn't exist return null; } else { return instance.getGroupMember(userId, group.getId()); } } /** * Gets all groups. * * @return a list of all groups * @throws GitLabApiException if the connection against GitLab failed */ public static List<GitLabGroupInfo> getGroups() throws GitLabApiException { return instance.getGroups(); } /** * Gets all groups accessible to a user * * @param userId ID of the user * @return a list of all groups * @throws GitLabApiException if the connection against GitLab failed */ public static List<GitLabGroupInfo> getGroupsAsUser(int userId) throws GitLabApiException { return instance.getGroupsAsUser(userId); } /** * Gets all groups owned by a user * * @param userId ID of the user * @return a list of all groups * @throws GitLabApiException if the connection against GitLab failed */ public static List<GitLabGroupInfo> getGroupsOwnedByUser(int userId) throws GitLabApiException { List<GitLabGroupInfo> groups = new ArrayList<GitLabGroupInfo>(getGroupsAsUser(userId)); Iterator<GitLabGroupInfo> iterator = groups.iterator(); // filter groups where the user isn't the owner while (iterator.hasNext()) { int groupId = iterator.next().getId(); if (!isGroupOwner(userId, groupId)) { iterator.remove(); } } return groups; } /** * Gets a group. * * @param groupId ID of the group. * @return a group for the ID or null if the group doesn't exist * @throws GitLabApiException if the connection against GitLab failed */ public static GitLabGroupInfo getGroup(int groupId) throws GitLabApiException { return instance.getGroup(groupId); } /** * Gets the group for a specified path. * * @param groupPath the group path * @return the group or null if the group doesn't exist * @throws GitLabApiException if the connection against GitLab failed */ public static GitLabGroupInfo getGroupByPath(String groupPath) throws GitLabApiException { return instance.getGroupByPath(groupPath); } /** * Checks whether a user is an administrator. * * @param userId ID of the user * @return true if the user is and administrator * @throws GitLabApiException if the connection against GitLab failed */ public static boolean isAdmin(int userId) throws GitLabApiException { GitLabUserInfo user = getUser(userId); // not administrator if the user wasn't found return user != null && user.isAdmin(); } /** * Gets whether a user is an owner of a group. * * @param userId ID of the user * @param groupId ID of the group * @return true if the user is an owner of the group * @throws GitLabApiException if the connection against GitLab failed */ public static boolean isGroupOwner(int userId, int groupId) throws GitLabApiException { return getAccessLevelInGroup(userId, groupId) == GitLabAccessLevel.OWNER; } /** * Get access level for a member in a group. * * @param userId ID of the member * @param groupId ID of the group * @return access level for the member * @throws GitLabApiException if the connection against GitLab failed */ public static GitLabAccessLevel getAccessLevelInGroup(int userId, int groupId) throws GitLabApiException { GitLabGroupMemberInfo member = getGroupMember(userId, groupId); // no access if user isn't a member of the group return member == null ? GitLabAccessLevel.NONE : member.getAccessLevel(); } /** * Gets the URL for a group. * * @param group the group * @return a string for the URL */ public static String getUrlForGroup(GitLabGroupInfo group) { return instance.getUrlForGroup(group); } /** * Returns the singleton implementation. * * @return the instance */ private static synchronized Implementation getInstance() { return instance; } /** * Changes the singleton implementation instance. * * @param instance a new instance */ private static synchronized void setInstance(Implementation instance) { GitLab.instance = instance; } /** * Singleton implementation. */ private static class Implementation { /** A cache storing users. */ private final LoadingCache<Integer, GitLabUserInfo> cachedUsers; /** A cache for storing memberships for groups. */ private final LoadingCache<Integer, Map<Integer, GitLabGroupMemberInfo>> cachedGroupMemberships; /** A cache for storing groups. */ private final LoadingCache<Integer, GitLabGroupRegistry> cachedGroups; /** * Creates a new standard implementation. */ public Implementation() { // use a standard cache builder this(CacheBuilder.newBuilder()); } /** * Creates a new implementation with a custom cache builder. * * @param cacheBuilder the cache builder to use. */ public Implementation(CacheBuilder cacheBuilder) { // cache for 1 minute cacheBuilder.expireAfterWrite(1, TimeUnit.MINUTES); // cache users with userId -> user cachedUsers = cacheBuilder.build(new UserCacheLoader()); // cache group members with groupId -> map of userId -> user cachedGroupMemberships = cacheBuilder.build(new GroupMembershipsCacheLoader()); // cache groups with user ID -> groups registry (user ID 0 for all users) cachedGroups = cacheBuilder.build(new GroupsCacheLoader()); } /** * @see GitLab#getUser(int) */ public GitLabUserInfo getUser(int userId) throws GitLabApiException { try { // throws UserNotFoundException if user is missing return cachedUsers.get(userId); } catch (ExecutionException e) { if (e.getCause() instanceof UserNotFoundException) { return null; } else if (e.getCause() instanceof GitLabApiException) { // throw any GitLabApiExceptions throw (GitLabApiException)e.getCause(); } else { // throw any other unexpected exceptions throw new RuntimeException(e.getCause()); } } } /** * @see GitLab#getGroupMember(int, int) */ public GitLabGroupMemberInfo getGroupMember(int userId, int groupId) throws GitLabApiException { try { // throws GroupNotFoundException if group is missing, returns null if user is missing from group return cachedGroupMemberships.get(groupId).get(userId); } catch (ExecutionException e) { if (e.getCause() instanceof GroupNotFoundException) { return null; } else if (e.getCause() instanceof GitLabApiException) { // throw any GitLabApiExceptions throw (GitLabApiException)e.getCause(); } else { // throw any other unexpected exceptions throw new RuntimeException(e.getCause()); } } } /** * @see GitLab#getGroups() */ public List<GitLabGroupInfo> getGroups() throws GitLabApiException { try { // user ID for all users return cachedGroups.get(0).asList(); } catch (ExecutionException e) { if (e.getCause() instanceof GitLabApiException) { // throw any GitLabApiExceptions throw (GitLabApiException)e.getCause(); } else { // throw any other unexpected exceptions throw new RuntimeException(e.getCause()); } } } /** * @see GitLab#getGroupsAsUser(int) */ public List<GitLabGroupInfo> getGroupsAsUser(int userId) throws GitLabApiException { try { checkArgument(userId > 0, "User ID must be positive"); return cachedGroups.get(userId).asList(); } catch (ExecutionException e) { if (e.getCause() instanceof GitLabApiException) { // throw any GitLabApiExceptions throw (GitLabApiException)e.getCause(); } else { // throw any other unexpected exceptions throw new RuntimeException(e.getCause()); } } } /** * @see GitLab#getGroup(int) */ public GitLabGroupInfo getGroup(int groupId) throws GitLabApiException { try { // get group by group ID or null if not found return cachedGroups.get(0).getById(groupId); } catch (ExecutionException e) { if (e.getCause() instanceof GitLabApiException) { // throw any GitLabApiExceptions throw (GitLabApiException)e.getCause(); } else { // throw any other unexpected exceptions throw new RuntimeException(e.getCause()); } } } /** * @see GitLab#getGroupByPath(String) */ public GitLabGroupInfo getGroupByPath(String path) throws GitLabApiException { try { // get group by path or null if not found return cachedGroups.get(0).getByPath(path); } catch (ExecutionException e) { if (e.getCause() instanceof GitLabApiException) { // throw any GitLabApiExceptions throw (GitLabApiException)e.getCause(); } else { // throw any other unexpected exceptions throw new RuntimeException(e.getCause()); } } } /** * @see GitLab#getUrlForGroup(GitLabGroupInfo) */ public String getUrlForGroup(GitLabGroupInfo group) { return String.format("%s/groups/%s", getApiClient().getHost(), group.getPath()); } /** * Cache loader for getting users from the API. */ private class UserCacheLoader extends CacheLoader<Integer, GitLabUserInfo> { @Override public GitLabUserInfo load(Integer userId) throws Exception { return getApiClient().getUser(userId); } } /** * Cache loader for getting memberships of groups from the API. */ private class GroupMembershipsCacheLoader extends CacheLoader<Integer, Map<Integer, GitLabGroupMemberInfo>> { @Override public Map<Integer, GitLabGroupMemberInfo> load(Integer groupId) throws Exception { // get all members as a list List<GitLabGroupMemberInfo> memberList = getApiClient().getGroupMembers(groupId); // create and return map with userId -> member Map<Integer, GitLabGroupMemberInfo> members = new HashMap<Integer, GitLabGroupMemberInfo>(memberList.size()); for (final GitLabGroupMemberInfo member : memberList) { members.put(member.getId(), member); } return members; } } /** * Cache loader for getting groups from the API. */ private class GroupsCacheLoader extends CacheLoader<Integer, GitLabGroupRegistry> { @Override public GitLabGroupRegistry load(Integer userId) throws Exception { final List<GitLabGroupInfo> groups; if (userId == 0) { // store all groups for user ID 0 groups = getApiClient().getGroups(); } else { // store groups accessible to only the user groups = getApiClient().asUser(userId).getGroups(); } // load the groups and put them in a registry return new GitLabGroupRegistry(groups); } } /** * Returns the API client. * * @return an API client */ private GitLabApiClient getApiClient() { return GitLabConfiguration.getApiClient(); } } }