blob: d661c9b425953be8e023058d38f2608d3ce96901 [file] [log] [blame]
/*
* Copyright 2013 gitblit.com.
*
* 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.
*/
package com.gitblit.manager;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.ConfigUserService;
import com.gitblit.Constants;
import com.gitblit.IStoredSettings;
import com.gitblit.IUserService;
import com.gitblit.Keys;
import com.gitblit.extensions.UserTeamLifeCycleListener;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
* The user manager manages persistence and retrieval of users and teams.
*
* @author James Moger
*
*/
@Singleton
public class UserManager implements IUserManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final IStoredSettings settings;
private final IRuntimeManager runtimeManager;
private final IPluginManager pluginManager;
private final Map<String, String> legacyBackingServices;
private IUserService userService;
@Inject
public UserManager(IRuntimeManager runtimeManager, IPluginManager pluginManager) {
this.settings = runtimeManager.getSettings();
this.runtimeManager = runtimeManager;
this.pluginManager = pluginManager;
// map of legacy realm backing user services
legacyBackingServices = new HashMap<String, String>();
legacyBackingServices.put("com.gitblit.HtpasswdUserService", "realm.htpasswd.backingUserService");
legacyBackingServices.put("com.gitblit.LdapUserService", "realm.ldap.backingUserService");
legacyBackingServices.put("com.gitblit.PAMUserService", "realm.pam.backingUserService");
legacyBackingServices.put("com.gitblit.RedmineUserService", "realm.redmine.backingUserService");
legacyBackingServices.put("com.gitblit.SalesforceUserService", "realm.salesforce.backingUserService");
legacyBackingServices.put("com.gitblit.WindowsUserService", "realm.windows.backingUserService");
}
/**
* Set the user service. The user service authenticates *local* users and is
* responsible for persisting and retrieving all users and all teams.
*
* @param userService
*/
public void setUserService(IUserService userService) {
this.userService = userService;
this.userService.setup(runtimeManager);
logger.info(userService.toString());
}
@Override
public void setup(IRuntimeManager runtimeManager) {
// NOOP
}
@Override
public UserManager start() {
if (this.userService == null) {
String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.conf");
IUserService service = null;
if (legacyBackingServices.containsKey(realm)) {
// create the user service from the legacy config
String realmKey = legacyBackingServices.get(realm);
logger.warn("");
logger.warn(Constants.BORDER2);
logger.warn(" Key '{}' is obsolete!", realmKey);
logger.warn(" Please set '{}={}'", Keys.realm.userService, settings.getString(realmKey, "${baseFolder}/users.conf"));
logger.warn(Constants.BORDER2);
logger.warn("");
File realmFile = runtimeManager.getFileOrFolder(realmKey, "${baseFolder}/users.conf");
service = createUserService(realmFile);
} else {
// either a file path OR a custom user service
try {
// check to see if this "file" is a custom user service class
Class<?> realmClass = Class.forName(realm);
service = (IUserService) realmClass.newInstance();
} catch (ClassNotFoundException t) {
// typical file path configuration
File realmFile = runtimeManager.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf");
service = createUserService(realmFile);
} catch (InstantiationException | IllegalAccessException e) {
logger.error("failed to instantiate user service {}: {}. Trying once again with IRuntimeManager constructor", realm, e.getMessage());
//try once again with IRuntimeManager constructor. This adds support for subclasses of ConfigUserService and other custom IUserServices
service = createIRuntimeManagerAwareUserService(realm);
}
}
setUserService(service);
}
return this;
}
/**
* Tries to create an {@link IUserService} with {@link #runtimeManager} as a constructor parameter
*
* @param realm the class name of the {@link IUserService} to be instantiated
* @return the {@link IUserService} or {@code null} if instantiation fails
*/
private IUserService createIRuntimeManagerAwareUserService(String realm) {
try {
Constructor<?> constructor = Class.forName(realm).getConstructor(IRuntimeManager.class);
return (IUserService) constructor.newInstance(runtimeManager);
} catch (NoSuchMethodException | SecurityException | ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
logger.error("failed to instantiate user service {}: {}", realm, e.getMessage());
return null;
}
}
protected IUserService createUserService(File realmFile) {
IUserService service = null;
if (realmFile.getName().toLowerCase().endsWith(".conf")) {
// config-based realm file
service = new ConfigUserService(realmFile);
}
assert service != null;
if (!realmFile.exists()) {
// Create the Administrator account for a new realm file
try {
realmFile.createNewFile();
} catch (IOException x) {
logger.error(MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmFile), x);
}
UserModel admin = new UserModel("admin");
admin.password = "admin";
admin.canAdmin = true;
admin.excludeFromFederation = true;
service.updateUserModel(admin);
}
return service;
}
@Override
public UserManager stop() {
return this;
}
/**
* Returns true if the username represents an internal account
*
* @param username
* @return true if the specified username represents an internal account
*/
@Override
public boolean isInternalAccount(String username) {
return !StringUtils.isEmpty(username)
&& (username.equalsIgnoreCase(Constants.FEDERATION_USER)
|| username.equalsIgnoreCase(UserModel.ANONYMOUS.username));
}
/**
* Returns the cookie value for the specified user.
*
* @param model
* @return cookie value
*/
@Override
public String getCookie(UserModel model) {
return userService.getCookie(model);
}
/**
* Retrieve the user object for the specified cookie.
*
* @param cookie
* @return a user object or null
*/
@Override
public UserModel getUserModel(char[] cookie) {
UserModel user = userService.getUserModel(cookie);
return user;
}
/**
* Retrieve the user object for the specified username.
*
* @param username
* @return a user object or null
*/
@Override
public UserModel getUserModel(String username) {
if (StringUtils.isEmpty(username)) {
return null;
}
String usernameDecoded = StringUtils.decodeUsername(username);
UserModel user = userService.getUserModel(usernameDecoded);
return user;
}
/**
* Updates/writes a complete user object.
*
* @param model
* @return true if update is successful
*/
@Override
public boolean updateUserModel(UserModel model) {
final boolean isCreate = null == userService.getUserModel(model.username);
if (userService.updateUserModel(model)) {
if (isCreate) {
callCreateUserListeners(model);
}
return true;
}
return false;
}
/**
* Updates/writes all specified user objects.
*
* @param models a list of user models
* @return true if update is successful
* @since 1.2.0
*/
@Override
public boolean updateUserModels(Collection<UserModel> models) {
return userService.updateUserModels(models);
}
/**
* Adds/updates a user object keyed by username. This method allows for
* renaming a user.
*
* @param username
* the old username
* @param model
* the user object to use for username
* @return true if update is successful
*/
@Override
public boolean updateUserModel(String username, UserModel model) {
final boolean isCreate = null == userService.getUserModel(username);
if (userService.updateUserModel(username, model)) {
if (isCreate) {
callCreateUserListeners(model);
}
return true;
}
return false;
}
/**
* Deletes the user object from the user service.
*
* @param model
* @return true if successful
*/
@Override
public boolean deleteUserModel(UserModel model) {
if (userService.deleteUserModel(model)) {
callDeleteUserListeners(model);
return true;
}
return false;
}
/**
* Delete the user object with the specified username
*
* @param username
* @return true if successful
*/
@Override
public boolean deleteUser(String username) {
if (StringUtils.isEmpty(username)) {
return false;
}
String usernameDecoded = StringUtils.decodeUsername(username);
UserModel user = getUserModel(usernameDecoded);
if (userService.deleteUser(usernameDecoded)) {
callDeleteUserListeners(user);
return true;
}
return false;
}
/**
* Returns the list of all users available to the login service.
*
* @return list of all usernames
*/
@Override
public List<String> getAllUsernames() {
List<String> names = new ArrayList<String>(userService.getAllUsernames());
return names;
}
/**
* Returns the list of all users available to the login service.
*
* @return list of all users
* @since 0.8.0
*/
@Override
public List<UserModel> getAllUsers() {
List<UserModel> users = userService.getAllUsers();
return users;
}
/**
* Returns the list of all teams available to the login service.
*
* @return list of all teams
* @since 0.8.0
*/
@Override
public List<String> getAllTeamNames() {
List<String> teams = userService.getAllTeamNames();
return teams;
}
/**
* Returns the list of all teams available to the login service.
*
* @return list of all teams
* @since 0.8.0
*/
@Override
public List<TeamModel> getAllTeams() {
List<TeamModel> teams = userService.getAllTeams();
return teams;
}
/**
* Returns the list of all teams who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @param role
* the repository name
* @return list of all teams that can bypass the access restriction
* @since 0.8.0
*/
@Override
public List<String> getTeamNamesForRepositoryRole(String role) {
List<String> teams = userService.getTeamNamesForRepositoryRole(role);
return teams;
}
/**
* Retrieve the team object for the specified team name.
*
* @param teamname
* @return a team object or null
* @since 0.8.0
*/
@Override
public TeamModel getTeamModel(String teamname) {
TeamModel team = userService.getTeamModel(teamname);
return team;
}
/**
* Updates/writes a complete team object.
*
* @param model
* @return true if update is successful
* @since 0.8.0
*/
@Override
public boolean updateTeamModel(TeamModel model) {
final boolean isCreate = null == userService.getTeamModel(model.name);
if (userService.updateTeamModel(model)) {
if (isCreate) {
callCreateTeamListeners(model);
}
return true;
}
return false;
}
/**
* Updates/writes all specified team objects.
*
* @param models a list of team models
* @return true if update is successful
* @since 1.2.0
*/
@Override
public boolean updateTeamModels(Collection<TeamModel> models) {
return userService.updateTeamModels(models);
}
/**
* Updates/writes and replaces a complete team object keyed by teamname.
* This method allows for renaming a team.
*
* @param teamname
* the old teamname
* @param model
* the team object to use for teamname
* @return true if update is successful
* @since 0.8.0
*/
@Override
public boolean updateTeamModel(String teamname, TeamModel model) {
final boolean isCreate = null == userService.getTeamModel(teamname);
if (userService.updateTeamModel(teamname, model)) {
if (isCreate) {
callCreateTeamListeners(model);
}
return true;
}
return false;
}
/**
* Deletes the team object from the user service.
*
* @param model
* @return true if successful
* @since 0.8.0
*/
@Override
public boolean deleteTeamModel(TeamModel model) {
if (userService.deleteTeamModel(model)) {
callDeleteTeamListeners(model);
return true;
}
return false;
}
/**
* Delete the team object with the specified teamname
*
* @param teamname
* @return true if successful
* @since 0.8.0
*/
@Override
public boolean deleteTeam(String teamname) {
TeamModel team = userService.getTeamModel(teamname);
if (userService.deleteTeam(teamname)) {
callDeleteTeamListeners(team);
return true;
}
return false;
}
/**
* Returns the list of all users who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @param role
* the repository name
* @return list of all usernames that can bypass the access restriction
* @since 0.8.0
*/
@Override
public List<String> getUsernamesForRepositoryRole(String role) {
return userService.getUsernamesForRepositoryRole(role);
}
/**
* Renames a repository role.
*
* @param oldRole
* @param newRole
* @return true if successful
*/
@Override
public boolean renameRepositoryRole(String oldRole, String newRole) {
return userService.renameRepositoryRole(oldRole, newRole);
}
/**
* Removes a repository role from all users.
*
* @param role
* @return true if successful
*/
@Override
public boolean deleteRepositoryRole(String role) {
return userService.deleteRepositoryRole(role);
}
protected void callCreateUserListeners(UserModel user) {
if (pluginManager == null || user == null) {
return;
}
for (UserTeamLifeCycleListener listener : pluginManager.getExtensions(UserTeamLifeCycleListener.class)) {
try {
listener.onCreation(user);
} catch (Throwable t) {
logger.error(String.format("failed to call plugin.onCreation%s", user.username), t);
}
}
}
protected void callCreateTeamListeners(TeamModel team) {
if (pluginManager == null || team == null) {
return;
}
for (UserTeamLifeCycleListener listener : pluginManager.getExtensions(UserTeamLifeCycleListener.class)) {
try {
listener.onCreation(team);
} catch (Throwable t) {
logger.error(String.format("failed to call plugin.onCreation %s", team.name), t);
}
}
}
protected void callDeleteUserListeners(UserModel user) {
if (pluginManager == null || user == null) {
return;
}
for (UserTeamLifeCycleListener listener : pluginManager.getExtensions(UserTeamLifeCycleListener.class)) {
try {
listener.onDeletion(user);
} catch (Throwable t) {
logger.error(String.format("failed to call plugin.onDeletion %s", user.username), t);
}
}
}
protected void callDeleteTeamListeners(TeamModel team) {
if (pluginManager == null || team == null) {
return;
}
for (UserTeamLifeCycleListener listener : pluginManager.getExtensions(UserTeamLifeCycleListener.class)) {
try {
listener.onDeletion(team);
} catch (Throwable t) {
logger.error(String.format("failed to call plugin.onDeletion %s", team.name), t);
}
}
}
}