Browse Source

Implemented ConfigUserService. Fixed and deprecated FileUserService.

tags/v0.8.0
James Moger 12 years ago
parent
commit
93f4729cdf

+ 1
- 0
.gitignore View File

@@ -23,3 +23,4 @@
/javadoc
/express
/build-demo.xml
/users.conf

+ 3
- 3
build.xml View File

@@ -103,7 +103,7 @@
<copy todir="${basedir}" overwrite="false">
<fileset dir="${basedir}/distrib">
<include name="gitblit.properties" />
<include name="users.properties" />
<include name="users.conf" />
</fileset>
</copy>
@@ -321,10 +321,10 @@
<delete dir="${project.war.dir}" />
<!-- Copy web.xml and users.properties to WEB-INF -->
<!-- Copy web.xml and users.conf to WEB-INF -->
<copy todir="${project.war.dir}/WEB-INF">
<fileset dir="${basedir}/distrib">
<include name="users.properties" />
<include name="users.conf" />
</fileset>
<fileset dir="${basedir}/src/WEB-INF">
<include name="web.xml" />

+ 3
- 2
distrib/gitblit.properties View File

@@ -50,13 +50,14 @@ web.authenticateAdminPages = true
# SINCE 0.5.0
web.allowCookieAuthentication = true
# Either the path to a simple user properties file
# Either the full path to a user config file (users.conf)
# OR the full path to a simple user properties file (users.properties)
# OR a fully qualified class name that implements the IUserService interface.
# Any custom implementation must have a public default constructor.
#
# SINCE 0.5.0
# RESTART REQUIRED
realm.userService = users.properties
realm.userService = users.conf
# How to store passwords.
# Valid values are plain, md5, or combined-md5. md5 is the hash of password.

+ 4
- 0
distrib/users.conf View File

@@ -0,0 +1,4 @@
[user "admin"]
password = admin
role = "#admin"
role = "#notfederated"

+ 0
- 3
distrib/users.properties View File

@@ -1,3 +0,0 @@
## Gitblit realm file format: username=password,\#permission,repository1,repository2...
#Fri Jul 22 14:27:08 EDT 2011
admin=admin,\#admin,\#notfederated

+ 31
- 9
docs/01_setup.mkd View File

@@ -2,11 +2,11 @@
1. Download [Gitblit WAR %VERSION%](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%) to the webapps folder of your servlet container.
2. You may have to manually extract the WAR (zip file) to a folder within your webapps folder.
3. Copy the `WEB-INF/users.properties` file to a location outside the webapps folder that is accessible by your servlet container.
3. Copy the `WEB-INF/users.conf` file to a location outside the webapps folder that is accessible by your servlet container.
4. The Gitblit webapp is configured through its `web.xml` file.
Open `web.xml` in your favorite text editor and make sure to review and set:
- &lt;context-parameter&gt; *git.repositoryFolder* (set the full path to your repositories folder)
- &lt;context-parameter&gt; *realm.userService* (set the full path to `users.properties`)
- &lt;context-parameter&gt; *realm.userService* (set the full path to `users.conf`)
5. You may have to restart your servlet container.
6. Open your browser to <http://localhost/gitblit> or whatever the url should be.
7. Enter the default administrator credentials: **admin / admin** and click the *Login* button
@@ -89,17 +89,22 @@ Command-Line parameters override the values in `gitblit.properties` at runtime.
**Example**
java -jar gitblit.jar --userService c:\myrealm.properties --storePassword something
java -jar gitblit.jar --userService c:\myrealm.config --storePassword something
## Upgrading Gitblit
Generally, upgrading is easy.
Since Gitblit does not use a database the only files you have to worry about are your configuration file (`gitblit.properties` or `web.xml`) and possibly your `users.properties` file.
Since Gitblit does not use a database the only files you have to worry about are your configuration file (`gitblit.properties` or `web.xml`) and possibly your `users.conf` or `users.properties` file.
Any important changes to the setting keys or default values will always be mentioned in the [release log](releases.html).
Gitblit v0.8.0 introduced a new default user service implementation which serializes and deserializes user objects into `users.conf`. A `users.conf` file will be automatically created from an existing `users.properties` file on the first launch after an upgrade. To use the `users.conf` service, *realm.userService=users.conf* must be set. This revised user service allows for more sophisticated Gitblit user objects and will facilitate the development of more advanced features without adding the complexity of an embedded SQL database.
`users.properties` and its user service implementation are deprecated as of v0.8.0.
### Upgrading Gitblit WAR
1. Backup your `web.xml` file
1. Backup your `web.xml` file
Backup your `web.properties` file (if you have one, these are the setting overrides from using the RPC administration service)
2. Delete currently deployed gitblit WAR
3. Deploy new WAR and overwrite the `web.xml` file with your backup
4. Review and optionally apply any new settings as indicated in the [release log](releases.html).
@@ -107,10 +112,14 @@ Any important changes to the setting keys or default values will always be menti
### Upgrading Gitblit GO
1. Backup your `gitblit.properties` file
2. Backup your `users.properties` file *(if it is located in the Gitblit GO folder)*
2. Backup your `users.properties` file *(if it is located in the Gitblit GO folder)*
OR
Backup your `users.conf` file *(if it is located in the Gitblit GO folder)*
3. Unzip Gitblit GO to a new folder
4. Overwrite the `gitblit.properties` file with your backup
5. Overwrite the `users.properties` file with your backup *(if it was located in the Gitblit GO folder)*
5. Overwrite the `users.properties` file with your backup *(if it was located in the Gitblit GO folder)*
OR
Overwrite the `users.conf` file with your backup *(if it was located in the Gitblit GO folder)*
6. Review and optionally apply any new settings as indicated in the [release log](releases.html).
#### Upgrading Windows Service
@@ -148,7 +157,20 @@ All repositories created with Gitblit are *bare* and will automatically have *.g
#### Repository Owner
The *Repository Owner* has the special permission of being able to edit a repository through the web UI. The Repository Owner is not permitted to rename the repository, delete the repository, or reassign ownership to another user.
### Administering Users
### Administering Users (Gitblit v0.8.0+)
All users are stored in the `users.conf` file or in the file you specified in `gitblit.properties`.<br/>
The `users.conf` file uses a Git-style configuration format:
[user "admin"]
password = admin
role = "#admin"
role = "#notfederated"
repository = repo1.git
repository = repo2.git
The `users.conf` file allows flexibility for adding new fields to a UserModel object that the original `users.properties` file does not afford without imposing the complexity of relying on an embedded SQL database.
### Administering Users (Gitblit v0.5.0 - v0.7.0)
All users are stored in the `users.properties` file or in the file you specified in `gitblit.properties`.<br/>
The format of `users.properties` follows Jetty's convention for HashRealms:
@@ -165,7 +187,7 @@ User passwords are CASE-SENSITIVE and may be *plain*, *md5*, or *combined-md5* f
There are two actual *roles* in Gitblit: *#admin*, which grants administrative powers to that user, and *#notfederated*, which prevents an account from being pulled by another Gitblit instance. Administrators automatically have access to all repositories. All other *roles* are repository names. If a repository is access-restricted, the user must have the repository's name within his/her roles to bypass the access restriction. This is how users are granted access to a restricted repository.
## Authentication and Authorization Customization
Instead of maintaining a `users.properties` file, you may want to integrate Gitblit into an existing environment.
Instead of maintaining a `users.conf` or `users.properties` file, you may want to integrate Gitblit into an existing environment.
You may use your own custom *com.gitblit.IUserService* implementation by specifying its fully qualified classname in the *realm.userService* setting.

+ 4
- 4
docs/02_federation.mkd View File

@@ -155,9 +155,9 @@ If they do not match, the repository is skipped and this is indicated in the log
By default all user accounts except the *admin* account are automatically pulled when using the *ALL* token or the *USERS_AND_REPOSITORIES* token. You may exclude a user account from being pulled by a federated Gitblit instance by checking *exclude from federation* in the edit user page.
The pulling Gitblit instance will store a registration-specific `users.properties` file for the pulled user accounts and their repository permissions. This file is stored in the *federation.N.folder* folder.
The pulling Gitblit instance will store a registration-specific `users.conf` file for the pulled user accounts and their repository permissions. This file is stored in the *federation.N.folder* folder.
If you specify *federation.N.mergeAccounts=true*, then the user accounts from the origin Gitblit instance will be integrated into the `users.properties` file of your Gitblit instance and allow sign-on of those users.
If you specify *federation.N.mergeAccounts=true*, then the user accounts from the origin Gitblit instance will be integrated into the `users.conf` file of your Gitblit instance and allow sign-on of those users.
**NOTE:**
Upgrades from older Gitblit versions will not have the *#notfederated* role assigned to the *admin* account. Without that role, your admin account WILL be transferred with an *ALL* or *USERS_AND_REPOSITORIES* token.
@@ -214,7 +214,7 @@ By default, federated repositories can not be pushed to, they are read-only by t
<tr><th>federation.N.mergeAccounts</th>
<td>boolean</td>
<td>if <b>true</b>, merge the retrieved accounts into the <code>users.properties</code> of <b>this</b> Gitblit instance.<br/><em>default is false</em></td>
<td>if <b>true</b>, merge the retrieved accounts into the <code>users.conf</code> of <b>this</b> Gitblit instance.<br/><em>default is false</em></td>
</tr>
<tr><th>federation.N.sendStatus</th>
@@ -248,7 +248,7 @@ These examples would be entered into the `gitblit.properties` file of the pullin
This assumes that the *token* is the *ALL* token from the origin gitblit instance.
The repositories, example1_users.properties, and example1_gitblit.properties will be put in *git.repositoriesFolder* and the origin user accounts will be merged into the local user accounts, including passwords and all roles. The Gitblit instance will also send a status acknowledgment to the origin Gitblit instance at the end of the pull operation. The status report will include the state of each repository pull (EXCLUDED, SKIPPED, NOCHANGE, PULLED, MIRRORED). This way the origin Gitblit instance can monitor the health of its mirrors.
The repositories, example1_users.conf, and example1_gitblit.properties will be put in *git.repositoriesFolder* and the origin user accounts will be merged into the local user accounts, including passwords and all roles. The Gitblit instance will also send a status acknowledgment to the origin Gitblit instance at the end of the pull operation. The status report will include the state of each repository pull (EXCLUDED, SKIPPED, NOCHANGE, PULLED, MIRRORED). This way the origin Gitblit instance can monitor the health of its mirrors.
This example is considered *nearly* perfect because while the origin Gitblit's server settings are pulled and saved locally, they are not merged with your server settings so its not a true mirror, but its likely the mirror you'd want to configure.

+ 3
- 3
docs/03_faq.mkd View File

@@ -34,7 +34,7 @@ Run the server as *root* (security concern) or change the ports you are serving
2. Confirm that the servlet container process has full read-write-execute permissions to your *git.repositoriesFolder*.
### Gitblit WAR will not authenticate any users?!
Confirm that the &lt;context-param&gt; *realm.userService* value in your `web.xml` file actually points to a `users.properties` file.
Confirm that the &lt;context-param&gt; *realm.userService* value in your `web.xml` file actually points to a `users.conf` or `users.properties` file.
### Gitblit won't open my grouped repository (/group/myrepo.git) or browse my log/branch/tag/ref?!
This is likely an url encoding/decoding problem with forward slashes:
@@ -111,9 +111,9 @@ Yes.
Gitblit will run just fine with a JRE. Gitblit can optionally use `keytool` from the JDK to generate self-signed certificates, but normally Gitblit uses [BouncyCastle][bouncycastle] for that need.
### Does Gitblit use a database to store its data?
No. Gitblit stores its repository configuration information within the `.git/config` file and its user information in `users.properties` or whatever filename is configured in `gitblit.properties`.
No. Gitblit stores its repository configuration information within the `.git/config` file and its user information in `users.conf`, `users.properties`, or whatever filename is configured in `gitblit.properties`.
### Can I manually edit users.properties, gitblit.properties, or .git/config?
### Can I manually edit users.conf, users.properties, gitblit.properties, or .git/config?
Yes. You can manually manipulate all of them and (most) changes will be immediately available to Gitblit.<br/>Exceptions to this are noted in `gitblit.properties`.
**NOTE:**

+ 5
- 1
docs/04_releases.mkd View File

@@ -3,13 +3,17 @@
### Current Release
**%VERSION%** ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%) | [war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%) | [express](http://code.google.com/p/gitblit/downloads/detail?name=%EXPRESS%) | [fedclient](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%) | [manager](http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%) | [api](http://code.google.com/p/gitblit/downloads/detail?name=%API%)) based on [%JGIT%][jgit] &nbsp; *released %BUILDDATE%*
- added: Gitblit Express bundle for running Gitblit on RedHat's OpenShift cloud
- added: new default user service implementation: com.gitblit.ConfigUserService (users.conf)
This user service implementation allows for serialization and deserialization of more sophisticated Gitblit User objects and will open the door for more advanced Gitblit features. For upgrading installations, a `users.conf` file will automatically be created for you from your existing `users.properties` file on your first launch of Gitblit. You will have to manually set *realm.userService=users.conf* to switch to the new user service. The original `users.properties` file and it's corresponding implementation are deprecated.
**New:** *realm.userService = users.conf*
- added: Gitblit Express bundle to get started running Gitblit on RedHat's OpenShift cloud
- added: optional Gravatar integration
**New:** *web.allowGravatar = true*
- added: multi-repository activity page. this is a timeline of commit activity over the last N days for one or more repositories.
**New:** *web.activityDuration = 14*
**New:** *web.timeFormat = HH:mm*
**New:** *web.datestampLongFormat = EEEE, MMMM d, yyyy*
- fixed: several a bugs in FileUserService related to cleaning up old repository permissions on a rename or delete
### Older Releases

+ 1
- 2
docs/05_roadmap.mkd View File

@@ -29,8 +29,6 @@ This list is volatile.
* push-restricted repositories would have anonymous R git:// access
* clone-restricted repositories would prohibit git:// access
* view-restricted repositories would prohibit git:// access
* Gitblit: Consider using Git-style config file instead of Java properties file for user storage (users.config vs. users.properties)
* this would allow for additional fields per user without bringing in a database
### TODO (low priority)
@@ -39,6 +37,7 @@ This list is volatile.
### IDEAS
* Gitblit: consider user-subscribed email notifications for a repository branch
* Gitblit: aggregate RSS feeds by tag or subfolder
* Gitblit: Consider creating more Git model objects and exposing them via the JSON RPC interface to allow inspection/retrieval of Git commits, Git trees, etc from Gitblit.
* Gitblit: Stronger ticgit integration (issue 8)

+ 471
- 0
src/com/gitblit/ConfigUserService.java View File

@@ -0,0 +1,471 @@
/*
* Copyright 2011 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;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
/**
* ConfigUserService is Gitblit's default user service implementation since
* version 0.8.0.
*
* Users and their repository memberships are stored in a git-style config file
* which is cached and dynamically reloaded when modified. This file is
* plain-text, human-readable, and may be edited with a text editor.
*
* Additionally, this format allows for expansion of the user model without
* bringing in the complexity of a database.
*
* @author James Moger
*
*/
public class ConfigUserService implements IUserService {
private final File realmFile;
private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class);
private final Map<String, UserModel> users = new ConcurrentHashMap<String, UserModel>();
private final Map<String, UserModel> cookies = new ConcurrentHashMap<String, UserModel>();
private final String userSection = "user";
private final String passwordField = "password";
private final String repositoryField = "repository";
private final String roleField = "role";
private volatile long lastModified;
public ConfigUserService(File realmFile) {
this.realmFile = realmFile;
}
/**
* Setup the user service.
*
* @param settings
* @since 0.6.1
*/
@Override
public void setup(IStoredSettings settings) {
}
/**
* Does the user service support cookie authentication?
*
* @return true or false
*/
@Override
public boolean supportsCookies() {
return true;
}
/**
* Returns the cookie value for the specified user.
*
* @param model
* @return cookie value
*/
@Override
public char[] getCookie(UserModel model) {
read();
UserModel storedModel = users.get(model.username.toLowerCase());
String cookie = StringUtils.getSHA1(model.username + storedModel.password);
return cookie.toCharArray();
}
/**
* Authenticate a user based on their cookie.
*
* @param cookie
* @return a user object or null
*/
@Override
public UserModel authenticate(char[] cookie) {
String hash = new String(cookie);
if (StringUtils.isEmpty(hash)) {
return null;
}
read();
UserModel model = null;
if (cookies.containsKey(hash)) {
model = cookies.get(hash);
}
return model;
}
/**
* Authenticate a user based on a username and password.
*
* @param username
* @param password
* @return a user object or null
*/
@Override
public UserModel authenticate(String username, char[] password) {
read();
UserModel returnedUser = null;
UserModel user = getUserModel(username);
if (user == null) {
return null;
}
if (user.password.startsWith(StringUtils.MD5_TYPE)) {
// password digest
String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));
if (user.password.equalsIgnoreCase(md5)) {
returnedUser = user;
}
} else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) {
// username+password digest
String md5 = StringUtils.COMBINED_MD5_TYPE
+ StringUtils.getMD5(username.toLowerCase() + new String(password));
if (user.password.equalsIgnoreCase(md5)) {
returnedUser = user;
}
} else if (user.password.equals(new String(password))) {
// plain-text password
returnedUser = user;
}
return returnedUser;
}
/**
* Retrieve the user object for the specified username.
*
* @param username
* @return a user object or null
*/
@Override
public UserModel getUserModel(String username) {
read();
UserModel model = users.get(username.toLowerCase());
return model;
}
/**
* Updates/writes a complete user object.
*
* @param model
* @return true if update is successful
*/
@Override
public boolean updateUserModel(UserModel model) {
return updateUserModel(model.username, model);
}
/**
* Updates/writes and replaces a complete 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) {
try {
read();
users.remove(username.toLowerCase());
users.put(model.username.toLowerCase(), model);
write();
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
t);
}
return false;
}
/**
* Deletes the user object from the user service.
*
* @param model
* @return true if successful
*/
@Override
public boolean deleteUserModel(UserModel model) {
return deleteUser(model.username);
}
/**
* Delete the user object with the specified username
*
* @param username
* @return true if successful
*/
@Override
public boolean deleteUser(String username) {
try {
// Read realm file
read();
users.remove(username.toLowerCase());
write();
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
}
return false;
}
/**
* Returns the list of all users available to the login service.
*
* @return list of all usernames
*/
@Override
public List<String> getAllUsernames() {
read();
List<String> list = new ArrayList<String>(users.keySet());
return list;
}
/**
* 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
*/
@Override
public List<String> getUsernamesForRepositoryRole(String role) {
List<String> list = new ArrayList<String>();
try {
read();
for (Map.Entry<String, UserModel> entry : users.entrySet()) {
UserModel model = entry.getValue();
if (model.hasRepository(role)) {
list.add(model.username);
}
}
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);
}
return list;
}
/**
* Sets the list of all uses who are allowed to bypass the access
* restriction placed on the specified repository.
*
* @param role
* the repository name
* @param usernames
* @return true if successful
*/
@Override
public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
try {
Set<String> specifiedUsers = new HashSet<String>();
for (String username : usernames) {
specifiedUsers.add(username.toLowerCase());
}
read();
// identify users which require add or remove role
for (UserModel user : users.values()) {
// user has role, check against revised user list
if (specifiedUsers.contains(user.username.toLowerCase())) {
user.addRepository(role);
} else {
// remove role from user
user.removeRepository(role);
}
}
// persist changes
write();
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);
}
return false;
}
/**
* Renames a repository role.
*
* @param oldRole
* @param newRole
* @return true if successful
*/
@Override
public boolean renameRepositoryRole(String oldRole, String newRole) {
try {
read();
// identify users which require role rename
for (UserModel model : users.values()) {
if (model.hasRepository(oldRole)) {
model.removeRepository(oldRole);
model.addRepository(newRole);
}
}
// persist changes
write();
return true;
} catch (Throwable t) {
logger.error(
MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);
}
return false;
}
/**
* Removes a repository role from all users.
*
* @param role
* @return true if successful
*/
@Override
public boolean deleteRepositoryRole(String role) {
try {
read();
// identify users which require role rename
for (UserModel user : users.values()) {
user.removeRepository(role);
}
// persist changes
write();
return true;
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
}
return false;
}
/**
* Writes the properties file.
*
* @param properties
* @throws IOException
*/
private synchronized void write() throws IOException {
// Write a temporary copy of the users file
File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect());
for (UserModel model : users.values()) {
config.setString(userSection, model.username, passwordField, model.password);
// user roles
List<String> roles = new ArrayList<String>();
if (model.canAdmin) {
roles.add(Constants.ADMIN_ROLE);
}
if (model.excludeFromFederation) {
roles.add(Constants.NOT_FEDERATED_ROLE);
}
config.setStringList(userSection, model.username, roleField, roles);
// repository memberships
config.setStringList(userSection, model.username, repositoryField,
new ArrayList<String>(model.repositories));
}
config.save();
// If the write is successful, delete the current file and rename
// the temporary copy to the original filename.
if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
if (realmFile.exists()) {
if (!realmFile.delete()) {
throw new IOException(MessageFormat.format("Failed to delete {0}!",
realmFile.getAbsolutePath()));
}
}
if (!realmFileCopy.renameTo(realmFile)) {
throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
realmFileCopy.getAbsolutePath(), realmFile.getAbsolutePath()));
}
} else {
throw new IOException(MessageFormat.format("Failed to save {0}!",
realmFileCopy.getAbsolutePath()));
}
}
/**
* Reads the realm file and rebuilds the in-memory lookup tables.
*/
protected synchronized void read() {
if (realmFile.exists() && (realmFile.lastModified() > lastModified)) {
lastModified = realmFile.lastModified();
users.clear();
cookies.clear();
try {
StoredConfig config = new FileBasedConfig(realmFile, FS.detect());
config.load();
Set<String> usernames = config.getSubsections(userSection);
for (String username : usernames) {
UserModel user = new UserModel(username);
user.password = config.getString(userSection, username, passwordField);
// user roles
Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
userSection, username, roleField)));
user.canAdmin = roles.contains(Constants.ADMIN_ROLE);
user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE);
// repository memberships
Set<String> repositories = new HashSet<String>(Arrays.asList(config
.getStringList(userSection, username, repositoryField)));
for (String repository : repositories) {
user.addRepository(repository);
}
// update cache
users.put(username, user);
cookies.put(StringUtils.getSHA1(username + user.password), user);
}
} catch (Exception e) {
logger.error(MessageFormat.format("Failed to read {0}", realmFile), e);
}
}
}
protected long lastModified() {
return lastModified;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + realmFile.getAbsolutePath() + ")";
}
}

+ 2
- 2
src/com/gitblit/FederationPullExecutor.java View File

@@ -285,9 +285,9 @@ public class FederationPullExecutor implements Runnable {
Collection<UserModel> users = FederationUtils.getUsers(registration);
if (users != null && users.size() > 0) {
File realmFile = new File(registrationFolderFile, registration.name
+ "_users.properties");
+ "_users.conf");
realmFile.delete();
FileUserService userService = new FileUserService(realmFile);
ConfigUserService userService = new ConfigUserService(realmFile);
for (UserModel user : users) {
userService.updateUserModel(user.username, user);

+ 17
- 10
src/com/gitblit/FileUserService.java View File

@@ -34,14 +34,19 @@ import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
/**
* FileUserService is Gitblit's default user service implementation.
* FileUserService is Gitblit's original default user service implementation.
*
* Users and their repository memberships are stored in a simple properties file
* which is cached and dynamically reloaded when modified.
*
* This class was deprecated in Gitblit 0.8.0 in favor of ConfigUserService
* which is still a human-readable, editable, plain-text file but it is more
* flexible for storing additional fields.
*
* @author James Moger
*
*/
@Deprecated
public class FileUserService extends FileSettings implements IUserService {
private final Logger logger = LoggerFactory.getLogger(FileUserService.class);
@@ -360,12 +365,11 @@ public class FileUserService extends FileSettings implements IUserService {
StringBuilder sb = new StringBuilder();
sb.append(password);
sb.append(',');
List<String> revisedRoles = new ArrayList<String>();
// skip first value (password)
for (int i = 1; i < values.length; i++) {
String value = values[i];
if (!value.equalsIgnoreCase(role)) {
revisedRoles.add(value);
sb.append(value);
sb.append(',');
}
@@ -406,7 +410,7 @@ public class FileUserService extends FileSettings implements IUserService {
for (int i = 1; i < roles.length; i++) {
String r = roles[i];
if (r.equalsIgnoreCase(oldRole)) {
needsRenameRole.remove(username);
needsRenameRole.add(username);
break;
}
}
@@ -420,13 +424,13 @@ public class FileUserService extends FileSettings implements IUserService {
StringBuilder sb = new StringBuilder();
sb.append(password);
sb.append(',');
List<String> revisedRoles = new ArrayList<String>();
revisedRoles.add(newRole);
sb.append(newRole);
sb.append(',');
// skip first value (password)
for (int i = 1; i < values.length; i++) {
String value = values[i];
if (!value.equalsIgnoreCase(oldRole)) {
revisedRoles.add(value);
sb.append(value);
sb.append(',');
}
@@ -467,7 +471,7 @@ public class FileUserService extends FileSettings implements IUserService {
for (int i = 1; i < roles.length; i++) {
String r = roles[i];
if (r.equalsIgnoreCase(role)) {
needsDeleteRole.remove(username);
needsDeleteRole.add(username);
break;
}
}
@@ -481,12 +485,10 @@ public class FileUserService extends FileSettings implements IUserService {
StringBuilder sb = new StringBuilder();
sb.append(password);
sb.append(',');
List<String> revisedRoles = new ArrayList<String>();
// skip first value (password)
for (int i = 1; i < values.length; i++) {
String value = values[i];
if (!value.equalsIgnoreCase(role)) {
revisedRoles.add(value);
sb.append(value);
sb.append(',');
}
@@ -558,4 +560,9 @@ public class FileUserService extends FileSettings implements IUserService {
}
return allUsers;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + propertiesFile.getAbsolutePath() + ")";
}
}

+ 32
- 6
src/com/gitblit/GitBlit.java View File

@@ -1435,6 +1435,7 @@ public class GitBlit implements ServletContextListener {
*
* @param settings
*/
@SuppressWarnings("deprecation")
public void configureContext(IStoredSettings settings, boolean startFederation) {
logger.info("Reading configuration from " + settings.toString());
this.settings = settings;
@@ -1453,20 +1454,45 @@ public class GitBlit implements ServletContextListener {
} catch (Throwable t) {
// not a login service class or class could not be instantiated.
// try to use default file login service
File realmFile = getFileOrFolder(Keys.realm.userService, "users.properties");
File realmFile = getFileOrFolder(Keys.realm.userService, "users.conf");
if (realmFile.exists()) {
// load the existing realm file
loginService = new FileUserService(realmFile);
if (realmFile.getName().toLowerCase().endsWith(".properties")) {
// load the v0.5.0 - v0.7.0 properties-based realm file
loginService = new FileUserService(realmFile);
// automatically create a users.conf realm file from the
// original users.properties file
File usersConfig = new File(realmFile.getParentFile(), "users.conf");
if (!usersConfig.exists()) {
logger.info(MessageFormat.format("Automatically creating {0} based on {1}",
usersConfig.getAbsolutePath(), realmFile.getAbsolutePath()));
ConfigUserService configService = new ConfigUserService(usersConfig);
for (String username : loginService.getAllUsernames()) {
UserModel userModel = loginService.getUserModel(username);
configService.updateUserModel(userModel);
}
}
// issue suggestion about switching to users.conf
logger.warn("Please consider using \"users.conf\" instead of the deprecated \"users.properties\" file");
} else if (realmFile.getName().toLowerCase().endsWith(".conf")) {
// load the config-based realm file
loginService = new ConfigUserService(realmFile);
}
} else {
// create a new realm file and add the default admin account.
// this is necessary for bootstrapping a dynamic environment
// like running on a cloud service.
// Create a new realm file and add the default admin
// account. This is necessary for bootstrapping a dynamic
// environment like running on a cloud service.
// As of v0.8.0 the default realm file is ConfigUserService.
try {
realmFile = getFileOrFolder(Keys.realm.userService, "users.conf");
realmFile.createNewFile();
loginService = new FileUserService(realmFile);
loginService = new ConfigUserService(realmFile);
UserModel admin = new UserModel("admin");
admin.password = "admin";
admin.canAdmin = true;
admin.excludeFromFederation = true;
loginService.updateUserModel(admin);
} catch (IOException x) {
logger.error(

+ 8
- 0
src/com/gitblit/models/UserModel.java View File

@@ -63,10 +63,18 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel>
return canAdmin || isOwner || repositories.contains(repository.name.toLowerCase());
}
public boolean hasRepository(String name) {
return repositories.contains(name.toLowerCase());
}
public void addRepository(String name) {
repositories.add(name.toLowerCase());
}
public void removeRepository(String name) {
repositories.remove(name.toLowerCase());
}
@Override
public String getName() {
return username;

+ 1
- 1
tests/com/gitblit/tests/FederationTests.java View File

@@ -47,7 +47,7 @@ public class FederationTests extends TestCase {
GitBlitServer.main("--httpPort", "" + port, "--httpsPort", "0", "--shutdownPort",
"" + shutdownPort, "--repositoriesFolder",
"\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", "--userService",
"distrib/users.properties");
"distrib/users.conf");
}
});

+ 4
- 3
tests/com/gitblit/tests/GitBlitSuite.java View File

@@ -25,8 +25,8 @@ import junit.framework.TestSuite;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepository;
import com.gitblit.ConfigUserService;
import com.gitblit.FileSettings;
import com.gitblit.FileUserService;
import com.gitblit.GitBlit;
import com.gitblit.GitBlitException;
import com.gitblit.GitBlitServer;
@@ -57,6 +57,7 @@ public class GitBlitSuite extends TestSetup {
suite.addTestSuite(JsonUtilsTest.class);
suite.addTestSuite(ByteFormatTest.class);
suite.addTestSuite(ObjectCacheTest.class);
suite.addTestSuite(UserServiceTest.class);
suite.addTestSuite(MarkdownUtilsTest.class);
suite.addTestSuite(JGitUtilsTest.class);
suite.addTestSuite(SyndicationUtilsTest.class);
@@ -91,7 +92,7 @@ public class GitBlitSuite extends TestSetup {
GitBlitServer.main("--httpPort", "" + port, "--httpsPort", "0", "--shutdownPort",
"" + shutdownPort, "--repositoriesFolder",
"\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", "--userService",
"distrib/users.properties");
"distrib/users.conf");
}
});
@@ -111,7 +112,7 @@ public class GitBlitSuite extends TestSetup {
protected void setUp() throws Exception {
FileSettings settings = new FileSettings("distrib/gitblit.properties");
GitBlit.self().configureContext(settings, true);
FileUserService loginService = new FileUserService(new File("distrib/users.properties"));
ConfigUserService loginService = new ConfigUserService(new File("distrib/users.conf"));
GitBlit.self().setUserService(loginService);
if (REPOSITORIES.exists() || REPOSITORIES.mkdirs()) {

+ 2
- 2
tests/com/gitblit/tests/GitBlitTest.java View File

@@ -96,7 +96,7 @@ public class GitBlitTest extends TestCase {
assertTrue(settings.getInteger("realm.realmFile", 5) == 5);
assertTrue(settings.getBoolean("git.enableGitServlet", false));
assertTrue(settings.getString("realm.userService", null).equals("users.properties"));
assertTrue(settings.getString("realm.userService", null).equals("users.conf"));
assertTrue(settings.getInteger("realm.minPasswordLength", 0) == 5);
List<String> mdExtensions = settings.getStrings("web.markdownExtensions");
assertTrue(mdExtensions.size() > 0);
@@ -117,7 +117,7 @@ public class GitBlitTest extends TestCase {
assertEquals(5, GitBlit.getInteger("realm.userService", 5));
assertTrue(GitBlit.getBoolean("git.enableGitServlet", false));
assertEquals("distrib/users.properties", GitBlit.getString("realm.userService", null));
assertEquals("distrib/users.conf", GitBlit.getString("realm.userService", null));
assertEquals(5, GitBlit.getInteger("realm.minPasswordLength", 0));
List<String> mdExtensions = GitBlit.getStrings("web.markdownExtensions");
assertTrue(mdExtensions.size() > 0);

+ 1
- 1
tests/com/gitblit/tests/GitServletTest.java View File

@@ -34,7 +34,7 @@ public class GitServletTest {
GitBlitServer.main("--httpPort", "" + port, "--httpsPort", "0", "--shutdownPort",
"" + shutdownPort, "--repositoriesFolder",
"\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", "--userService",
"distrib/users.properties");
"distrib/users.conf");
}
});

+ 1
- 1
tests/com/gitblit/tests/SyndicationUtilsTest.java View File

@@ -62,7 +62,7 @@ public class SyndicationUtilsTest extends TestCase {
Set<String> links = new HashSet<String>();
for (int i = 0; i < 2; i++) {
List<FeedEntryModel> feed = SyndicationUtils.readFeed(GitBlitSuite.url,
"ticgit.git", "master", 5, i, GitBlitSuite.account,
"ticgit.git", "deving", 5, i, GitBlitSuite.account,
GitBlitSuite.password.toCharArray());
assertTrue(feed != null);
assertTrue(feed.size() > 0);

+ 100
- 0
tests/com/gitblit/tests/UserServiceTest.java View File

@@ -0,0 +1,100 @@
/*
* Copyright 2011 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.tests;
import java.io.File;
import java.io.IOException;
import junit.framework.TestCase;
import com.gitblit.ConfigUserService;
import com.gitblit.FileUserService;
import com.gitblit.IUserService;
import com.gitblit.models.UserModel;
public class UserServiceTest extends TestCase {
public void testFileUserService() throws IOException {
File file = new File("us-test.properties");
file.delete();
test(new FileUserService(file));
file.delete();
}
public void testConfigUserService() throws IOException {
File file = new File("us-test.conf");
file.delete();
test(new ConfigUserService(file));
file.delete();
}
protected void test(IUserService service) {
UserModel admin = service.getUserModel("admin");
assertTrue(admin == null);
// add admin
admin = new UserModel("admin");
admin.password = "password";
admin.canAdmin = true;
admin.excludeFromFederation = true;
service.updateUserModel(admin);
admin = null;
// add new user
UserModel newUser = new UserModel("test");
newUser.password = "testPassword";
newUser.addRepository("repo1");
newUser.addRepository("repo2");
newUser.addRepository("sub/repo3");
service.updateUserModel(newUser);
// add one more new user and then test reload of first new user
newUser = new UserModel("garbage");
newUser.password = "garbage";
service.updateUserModel(newUser);
// confirm all added users
assertEquals(3, service.getAllUsernames().size());
// confirm reloaded test user
newUser = service.getUserModel("test");
assertEquals("testPassword", newUser.password);
assertEquals(3, newUser.repositories.size());
assertTrue(newUser.hasRepository("repo1"));
assertTrue(newUser.hasRepository("repo2"));
assertTrue(newUser.hasRepository("sub/repo3"));
// confirm authentication of test user
UserModel testUser = service.authenticate("test", "testPassword".toCharArray());
assertEquals("test", testUser.username);
assertEquals("testPassword", testUser.password);
// delete a repository role and confirm role removal from test user
service.deleteRepositoryRole("repo2");
testUser = service.getUserModel("test");
assertEquals(2, testUser.repositories.size());
// delete garbage user and confirm user count
service.deleteUser("garbage");
assertEquals(2, service.getAllUsernames().size());
// rename repository and confirm role change for test user
service.renameRepositoryRole("repo1", "newrepo1");
testUser = service.getUserModel("test");
assertTrue(testUser.hasRepository("newrepo1"));
}
}

Loading…
Cancel
Save