Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago Finer-grained repository access permissions (issue 36)
Implemented discrete repository access permissions to replace the
really primitive course-grained permissions used to this point. This
implementation allows for finer-grained access control, but still
falls short of integrated, branch-based permissions sought by some.
Access permissions follow the conventions established by Gitosis and
Gitolite so they should feel immediately comfortable to experienced
users. This permissions infrastructure is complete and works exactly as
expected. Unfortunately, there is no ui in this commit to change
permissions, that will be forthcoming. In the meantime, Gitblit
hot-reloads users.conf so the permissions can be manipulated at runtime
with a text editor.
The following per-repository permissions are now supported:
- V (view in web ui, RSS feeds, download zip)
- R (clone)
- RW (clone and push)
- RWC (clone and push with ref creation)
- RWD (clone and push with ref creation, deletion)
- RW+ (clone and push with ref creation, deletion, rewind)
And a users.conf entry looks something like this:
[user "hannibal"]
password = bossman
repository = RWD:topsecret.git
11 years ago |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956 |
- /*
- * 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.Collection;
- import java.util.Collections;
- 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.Constants.AccessPermission;
- import com.gitblit.Constants.AccountType;
- import com.gitblit.manager.IRuntimeManager;
- import com.gitblit.models.TeamModel;
- import com.gitblit.models.UserModel;
- import com.gitblit.models.UserRepositoryPreferences;
- import com.gitblit.utils.ArrayUtils;
- import com.gitblit.utils.DeepCopier;
- 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 static final String TEAM = "team";
-
- private static final String USER = "user";
-
- private static final String PASSWORD = "password";
-
- private static final String DISPLAYNAME = "displayName";
-
- private static final String EMAILADDRESS = "emailAddress";
-
- private static final String ORGANIZATIONALUNIT = "organizationalUnit";
-
- private static final String ORGANIZATION = "organization";
-
- private static final String LOCALITY = "locality";
-
- private static final String STATEPROVINCE = "stateProvince";
-
- private static final String COUNTRYCODE = "countryCode";
-
- private static final String COOKIE = "cookie";
-
- private static final String REPOSITORY = "repository";
-
- private static final String ROLE = "role";
-
- private static final String MAILINGLIST = "mailingList";
-
- private static final String PRERECEIVE = "preReceiveScript";
-
- private static final String POSTRECEIVE = "postReceiveScript";
-
- private static final String STARRED = "starred";
-
- private static final String LOCALE = "locale";
-
- private static final String ACCOUNTTYPE = "accountType";
-
- 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 Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();
-
- private volatile long lastModified;
-
- private volatile boolean forceReload;
-
- public ConfigUserService(File realmFile) {
- this.realmFile = realmFile;
- }
-
- /**
- * Setup the user service.
- *
- * @param runtimeManager
- * @since 1.4.0
- */
- @Override
- public void setup(IRuntimeManager runtimeManager) {
- }
-
- /**
- * Returns the cookie value for the specified user.
- *
- * @param model
- * @return cookie value
- */
- @Override
- public String getCookie(UserModel model) {
- if (!StringUtils.isEmpty(model.cookie)) {
- return model.cookie;
- }
- UserModel storedModel = getUserModel(model.username);
- if (storedModel == null) {
- return null;
- }
- return storedModel.cookie;
- }
-
- /**
- * Gets the user object for the specified cookie.
- *
- * @param cookie
- * @return a user object or null
- */
- @Override
- public synchronized UserModel getUserModel(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);
- }
-
- if (model != null) {
- // clone the model, otherwise all changes to this object are
- // live and unpersisted
- model = DeepCopier.copy(model);
- }
- return model;
- }
-
- /**
- * Retrieve the user object for the specified username.
- *
- * @param username
- * @return a user object or null
- */
- @Override
- public synchronized UserModel getUserModel(String username) {
- read();
- UserModel model = users.get(username.toLowerCase());
- if (model != null) {
- // clone the model, otherwise all changes to this object are
- // live and unpersisted
- model = DeepCopier.copy(model);
- }
- 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 all specified user objects.
- *
- * @param models a list of user models
- * @return true if update is successful
- * @since 1.2.0
- */
- @Override
- public synchronized boolean updateUserModels(Collection<UserModel> models) {
- try {
- read();
- for (UserModel model : models) {
- UserModel originalUser = users.remove(model.username.toLowerCase());
- users.put(model.username.toLowerCase(), model);
- // null check on "final" teams because JSON-sourced UserModel
- // can have a null teams object
- if (model.teams != null) {
- for (TeamModel team : model.teams) {
- TeamModel t = teams.get(team.name.toLowerCase());
- if (t == null) {
- // new team
- team.addUser(model.username);
- teams.put(team.name.toLowerCase(), team);
- } else {
- // do not clobber existing team definition
- // maybe because this is a federated user
- t.addUser(model.username);
- }
- }
-
- // check for implicit team removal
- if (originalUser != null) {
- for (TeamModel team : originalUser.teams) {
- if (!model.isTeamMember(team.name)) {
- team.removeUser(model.username);
- }
- }
- }
- }
- }
- write();
- return true;
- } catch (Throwable t) {
- logger.error(MessageFormat.format("Failed to update user {0} models!", models.size()),
- t);
- }
- return false;
- }
-
- /**
- * 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 synchronized boolean updateUserModel(String username, UserModel model) {
- UserModel originalUser = null;
- try {
- if (!model.isLocalAccount()) {
- // do not persist password
- model.password = Constants.EXTERNAL_ACCOUNT;
- }
- read();
- originalUser = users.remove(username.toLowerCase());
- users.put(model.username.toLowerCase(), model);
- // null check on "final" teams because JSON-sourced UserModel
- // can have a null teams object
- if (model.teams != null) {
- for (TeamModel team : model.teams) {
- TeamModel t = teams.get(team.name.toLowerCase());
- if (t == null) {
- // new team
- team.addUser(username);
- teams.put(team.name.toLowerCase(), team);
- } else {
- // do not clobber existing team definition
- // maybe because this is a federated user
- t.removeUser(username);
- t.addUser(model.username);
- }
- }
-
- // check for implicit team removal
- if (originalUser != null) {
- for (TeamModel team : originalUser.teams) {
- if (!model.isTeamMember(team.name)) {
- team.removeUser(username);
- }
- }
- }
- }
- write();
- return true;
- } catch (Throwable t) {
- if (originalUser != null) {
- // restore original user
- users.put(originalUser.username.toLowerCase(), originalUser);
- } else {
- // drop attempted add
- users.remove(model.username.toLowerCase());
- }
- 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 synchronized boolean deleteUser(String username) {
- try {
- // Read realm file
- read();
- UserModel model = users.remove(username.toLowerCase());
- if (model == null) {
- // user does not exist
- return false;
- }
- // remove user from team
- for (TeamModel team : model.teams) {
- TeamModel t = teams.get(team.name);
- if (t == null) {
- // new team
- team.removeUser(username);
- teams.put(team.name.toLowerCase(), team);
- } else {
- // existing team
- t.removeUser(username);
- }
- }
- write();
- return true;
- } catch (Throwable t) {
- logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
- }
- return false;
- }
-
- /**
- * 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() {
- read();
- List<String> list = new ArrayList<String>(teams.keySet());
- Collections.sort(list);
- return list;
- }
-
- /**
- * Returns the list of all teams available to the login service.
- *
- * @return list of all teams
- * @since 0.8.0
- */
- @Override
- public synchronized List<TeamModel> getAllTeams() {
- read();
- List<TeamModel> list = new ArrayList<TeamModel>(teams.values());
- list = DeepCopier.copy(list);
- Collections.sort(list);
- 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 synchronized List<String> getTeamNamesForRepositoryRole(String role) {
- List<String> list = new ArrayList<String>();
- try {
- read();
- for (Map.Entry<String, TeamModel> entry : teams.entrySet()) {
- TeamModel model = entry.getValue();
- if (model.hasRepositoryPermission(role)) {
- list.add(model.name);
- }
- }
- } catch (Throwable t) {
- logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);
- }
- Collections.sort(list);
- return list;
- }
-
- /**
- * Retrieve the team object for the specified team name.
- *
- * @param teamname
- * @return a team object or null
- * @since 0.8.0
- */
- @Override
- public synchronized TeamModel getTeamModel(String teamname) {
- read();
- TeamModel model = teams.get(teamname.toLowerCase());
- if (model != null) {
- // clone the model, otherwise all changes to this object are
- // live and unpersisted
- model = DeepCopier.copy(model);
- }
- return model;
- }
-
- /**
- * Updates/writes a complete team object.
- *
- * @param model
- * @return true if update is successful
- * @since 0.8.0
- */
- @Override
- public boolean updateTeamModel(TeamModel model) {
- return updateTeamModel(model.name, model);
- }
-
- /**
- * 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) {
- try {
- read();
- for (TeamModel team : models) {
- teams.put(team.name.toLowerCase(), team);
- }
- write();
- return true;
- } catch (Throwable t) {
- logger.error(MessageFormat.format("Failed to update team {0} models!", models.size()), t);
- }
- return false;
- }
-
- /**
- * 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) {
- TeamModel original = null;
- try {
- read();
- original = teams.remove(teamname.toLowerCase());
- teams.put(model.name.toLowerCase(), model);
- write();
- return true;
- } catch (Throwable t) {
- if (original != null) {
- // restore original team
- teams.put(original.name.toLowerCase(), original);
- } else {
- // drop attempted add
- teams.remove(model.name.toLowerCase());
- }
- logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);
- }
- 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) {
- return deleteTeam(model.name);
- }
-
- /**
- * Delete the team object with the specified teamname
- *
- * @param teamname
- * @return true if successful
- * @since 0.8.0
- */
- @Override
- public boolean deleteTeam(String teamname) {
- try {
- // Read realm file
- read();
- teams.remove(teamname.toLowerCase());
- write();
- return true;
- } catch (Throwable t) {
- logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), 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());
- Collections.sort(list);
- return list;
- }
-
- /**
- * Returns the list of all users available to the login service.
- *
- * @return list of all usernames
- */
- @Override
- public synchronized List<UserModel> getAllUsers() {
- read();
- List<UserModel> list = new ArrayList<UserModel>(users.values());
- list = DeepCopier.copy(list);
- Collections.sort(list);
- 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 synchronized 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.hasRepositoryPermission(role)) {
- list.add(model.username);
- }
- }
- } catch (Throwable t) {
- logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);
- }
- Collections.sort(list);
- return list;
- }
-
- /**
- * Renames a repository role.
- *
- * @param oldRole
- * @param newRole
- * @return true if successful
- */
- @Override
- public synchronized boolean renameRepositoryRole(String oldRole, String newRole) {
- try {
- read();
- // identify users which require role rename
- for (UserModel model : users.values()) {
- if (model.hasRepositoryPermission(oldRole)) {
- AccessPermission permission = model.removeRepositoryPermission(oldRole);
- model.setRepositoryPermission(newRole, permission);
- }
- }
-
- // identify teams which require role rename
- for (TeamModel model : teams.values()) {
- if (model.hasRepositoryPermission(oldRole)) {
- AccessPermission permission = model.removeRepositoryPermission(oldRole);
- model.setRepositoryPermission(newRole, permission);
- }
- }
- // 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 synchronized boolean deleteRepositoryRole(String role) {
- try {
- read();
-
- // identify users which require role rename
- for (UserModel user : users.values()) {
- user.removeRepositoryPermission(role);
- }
-
- // identify teams which require role rename
- for (TeamModel team : teams.values()) {
- team.removeRepositoryPermission(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.
- *
- * @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());
-
- // write users
- for (UserModel model : users.values()) {
- if (!StringUtils.isEmpty(model.password)) {
- config.setString(USER, model.username, PASSWORD, model.password);
- }
- if (!StringUtils.isEmpty(model.cookie)) {
- config.setString(USER, model.username, COOKIE, model.cookie);
- }
- if (!StringUtils.isEmpty(model.displayName)) {
- config.setString(USER, model.username, DISPLAYNAME, model.displayName);
- }
- if (!StringUtils.isEmpty(model.emailAddress)) {
- config.setString(USER, model.username, EMAILADDRESS, model.emailAddress);
- }
- if (model.accountType != null) {
- config.setString(USER, model.username, ACCOUNTTYPE, model.accountType.name());
- }
- if (!StringUtils.isEmpty(model.organizationalUnit)) {
- config.setString(USER, model.username, ORGANIZATIONALUNIT, model.organizationalUnit);
- }
- if (!StringUtils.isEmpty(model.organization)) {
- config.setString(USER, model.username, ORGANIZATION, model.organization);
- }
- if (!StringUtils.isEmpty(model.locality)) {
- config.setString(USER, model.username, LOCALITY, model.locality);
- }
- if (!StringUtils.isEmpty(model.stateProvince)) {
- config.setString(USER, model.username, STATEPROVINCE, model.stateProvince);
- }
- if (!StringUtils.isEmpty(model.countryCode)) {
- config.setString(USER, model.username, COUNTRYCODE, model.countryCode);
- }
- if (model.getPreferences() != null) {
- if (!StringUtils.isEmpty(model.getPreferences().locale)) {
- config.setString(USER, model.username, LOCALE, model.getPreferences().locale);
- }
- }
-
- // user roles
- List<String> roles = new ArrayList<String>();
- if (model.canAdmin) {
- roles.add(Constants.ADMIN_ROLE);
- }
- if (model.canFork) {
- roles.add(Constants.FORK_ROLE);
- }
- if (model.canCreate) {
- roles.add(Constants.CREATE_ROLE);
- }
- if (model.excludeFromFederation) {
- roles.add(Constants.NOT_FEDERATED_ROLE);
- }
- if (roles.size() == 0) {
- // we do this to ensure that user record with no password
- // is written. otherwise, StoredConfig optimizes that account
- // away. :(
- roles.add(Constants.NO_ROLE);
- }
- config.setStringList(USER, model.username, ROLE, roles);
-
- // discrete repository permissions
- if (model.permissions != null && !model.canAdmin) {
- List<String> permissions = new ArrayList<String>();
- for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
- if (entry.getValue().exceeds(AccessPermission.NONE)) {
- permissions.add(entry.getValue().asRole(entry.getKey()));
- }
- }
- config.setStringList(USER, model.username, REPOSITORY, permissions);
- }
-
- // user preferences
- if (model.getPreferences() != null) {
- List<String> starred = model.getPreferences().getStarredRepositories();
- if (starred.size() > 0) {
- config.setStringList(USER, model.username, STARRED, starred);
- }
- }
- }
-
- // write teams
- for (TeamModel model : teams.values()) {
- // team roles
- List<String> roles = new ArrayList<String>();
- if (model.canAdmin) {
- roles.add(Constants.ADMIN_ROLE);
- }
- if (model.canFork) {
- roles.add(Constants.FORK_ROLE);
- }
- if (model.canCreate) {
- roles.add(Constants.CREATE_ROLE);
- }
- if (roles.size() == 0) {
- // we do this to ensure that team record is written.
- // Otherwise, StoredConfig might optimizes that record away.
- roles.add(Constants.NO_ROLE);
- }
- config.setStringList(TEAM, model.name, ROLE, roles);
- if (model.accountType != null) {
- config.setString(TEAM, model.name, ACCOUNTTYPE, model.accountType.name());
- }
-
- if (!model.canAdmin) {
- // write team permission for non-admin teams
- if (model.permissions == null) {
- // null check on "final" repositories because JSON-sourced TeamModel
- // can have a null repositories object
- if (!ArrayUtils.isEmpty(model.repositories)) {
- config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>(
- model.repositories));
- }
- } else {
- // discrete repository permissions
- List<String> permissions = new ArrayList<String>();
- for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
- if (entry.getValue().exceeds(AccessPermission.NONE)) {
- // code:repository (e.g. RW+:~james/myrepo.git
- permissions.add(entry.getValue().asRole(entry.getKey()));
- }
- }
- config.setStringList(TEAM, model.name, REPOSITORY, permissions);
- }
- }
-
- // null check on "final" users because JSON-sourced TeamModel
- // can have a null users object
- if (!ArrayUtils.isEmpty(model.users)) {
- config.setStringList(TEAM, model.name, USER, new ArrayList<String>(model.users));
- }
-
- // null check on "final" mailing lists because JSON-sourced
- // TeamModel can have a null users object
- if (!ArrayUtils.isEmpty(model.mailingLists)) {
- config.setStringList(TEAM, model.name, MAILINGLIST, new ArrayList<String>(
- model.mailingLists));
- }
-
- // null check on "final" preReceiveScripts because JSON-sourced
- // TeamModel can have a null preReceiveScripts object
- if (!ArrayUtils.isEmpty(model.preReceiveScripts)) {
- config.setStringList(TEAM, model.name, PRERECEIVE, model.preReceiveScripts);
- }
-
- // null check on "final" postReceiveScripts because JSON-sourced
- // TeamModel can have a null postReceiveScripts object
- if (!ArrayUtils.isEmpty(model.postReceiveScripts)) {
- config.setStringList(TEAM, model.name, POSTRECEIVE, model.postReceiveScripts);
- }
- }
-
- config.save();
- // manually set the forceReload flag because not all JVMs support real
- // millisecond resolution of lastModified. (issue-55)
- forceReload = true;
-
- // 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() && (forceReload || (realmFile.lastModified() != lastModified))) {
- forceReload = false;
- lastModified = realmFile.lastModified();
- users.clear();
- cookies.clear();
- teams.clear();
-
- try {
- StoredConfig config = new FileBasedConfig(realmFile, FS.detect());
- config.load();
- Set<String> usernames = config.getSubsections(USER);
- for (String username : usernames) {
- UserModel user = new UserModel(username.toLowerCase());
- user.password = config.getString(USER, username, PASSWORD);
- user.displayName = config.getString(USER, username, DISPLAYNAME);
- user.emailAddress = config.getString(USER, username, EMAILADDRESS);
- user.accountType = AccountType.fromString(config.getString(USER, username, ACCOUNTTYPE));
- if (Constants.EXTERNAL_ACCOUNT.equals(user.password) && user.accountType.isLocal()) {
- user.accountType = null;
- }
- user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT);
- user.organization = config.getString(USER, username, ORGANIZATION);
- user.locality = config.getString(USER, username, LOCALITY);
- user.stateProvince = config.getString(USER, username, STATEPROVINCE);
- user.countryCode = config.getString(USER, username, COUNTRYCODE);
- user.cookie = config.getString(USER, username, COOKIE);
- user.getPreferences().locale = config.getString(USER, username, LOCALE);
- if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) {
- user.cookie = StringUtils.getSHA1(user.username + user.password);
- }
-
- // user roles
- Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
- USER, username, ROLE)));
- user.canAdmin = roles.contains(Constants.ADMIN_ROLE);
- user.canFork = roles.contains(Constants.FORK_ROLE);
- user.canCreate = roles.contains(Constants.CREATE_ROLE);
- user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE);
-
- // repository memberships
- if (!user.canAdmin) {
- // non-admin, read permissions
- Set<String> repositories = new HashSet<String>(Arrays.asList(config
- .getStringList(USER, username, REPOSITORY)));
- for (String repository : repositories) {
- user.addRepositoryPermission(repository);
- }
- }
-
- // starred repositories
- Set<String> starred = new HashSet<String>(Arrays.asList(config
- .getStringList(USER, username, STARRED)));
- for (String repository : starred) {
- UserRepositoryPreferences prefs = user.getPreferences().getRepositoryPreferences(repository);
- prefs.starred = true;
- }
-
- // update cache
- users.put(user.username, user);
- if (!StringUtils.isEmpty(user.cookie)) {
- cookies.put(user.cookie, user);
- }
- }
-
- // load the teams
- Set<String> teamnames = config.getSubsections(TEAM);
- for (String teamname : teamnames) {
- TeamModel team = new TeamModel(teamname);
- Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
- TEAM, teamname, ROLE)));
- team.canAdmin = roles.contains(Constants.ADMIN_ROLE);
- team.canFork = roles.contains(Constants.FORK_ROLE);
- team.canCreate = roles.contains(Constants.CREATE_ROLE);
- team.accountType = AccountType.fromString(config.getString(TEAM, teamname, ACCOUNTTYPE));
-
- if (!team.canAdmin) {
- // non-admin team, read permissions
- team.addRepositoryPermissions(Arrays.asList(config.getStringList(TEAM, teamname,
- REPOSITORY)));
- }
- team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER)));
- team.addMailingLists(Arrays.asList(config.getStringList(TEAM, teamname,
- MAILINGLIST)));
- team.preReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM,
- teamname, PRERECEIVE)));
- team.postReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM,
- teamname, POSTRECEIVE)));
-
- teams.put(team.name.toLowerCase(), team);
-
- // set the teams on the users
- for (String user : team.users) {
- UserModel model = users.get(user);
- if (model != null) {
- model.teams.add(team);
- }
- }
- }
- } 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() + ")";
- }
- }
|