James Moger
2013-07-23 2659e7430ff27adf324abad961bfe461599f1618
Added PAMUserService for local Linux/Unix/MacOSX account authentication
1 files added
10 files modified
194 ■■■■■ changed files
.classpath 1 ●●●● patch | view | raw | blame | history
build.moxie 1 ●●●● patch | view | raw | blame | history
build.xml 2 ●●●●● patch | view | raw | blame | history
gitblit.iml 11 ●●●●● patch | view | raw | blame | history
releases.moxie 3 ●●●●● patch | view | raw | blame | history
src/main/distrib/data/gitblit.properties 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Constants.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/PAMUserService.java 142 ●●●●● patch | view | raw | blame | history
src/site/features.mkd 1 ●●●● patch | view | raw | blame | history
src/site/setup_authentication.mkd 8 ●●●●● patch | view | raw | blame | history
src/site/siteindex.mkd 7 ●●●●● patch | view | raw | blame | history
.classpath
@@ -45,6 +45,7 @@
    <classpathentry kind="lib" path="ext/platform-3.5.0.jar" sourcepath="ext/src/platform-3.5.0.jar" />
    <classpathentry kind="lib" path="ext/jna-3.5.0.jar" sourcepath="ext/src/jna-3.5.0.jar" />
    <classpathentry kind="lib" path="ext/guava-13.0.1.jar" sourcepath="ext/src/guava-13.0.1.jar" />
    <classpathentry kind="lib" path="ext/libpam4j-1.7.jar" sourcepath="ext/src/libpam4j-1.7.jar" />
    <classpathentry kind="lib" path="ext/junit-4.11.jar" sourcepath="ext/src/junit-4.11.jar" />
    <classpathentry kind="lib" path="ext/hamcrest-core-1.3.jar" sourcepath="ext/src/hamcrest-core-1.3.jar" />
    <classpathentry kind="lib" path="ext/selenium-java-2.28.0.jar" sourcepath="ext/src/selenium-java-2.28.0.jar" />
build.moxie
@@ -150,6 +150,7 @@
- compile 'com.force.api:force-partner-api:24.0.0' :war
- compile 'org.freemarker:freemarker:2.3.19' :war
- compile 'com.github.dblock.waffle:waffle-jna:1.5' :war
- compile 'org.kohsuke:libpam4j:1.7' :war
- test 'junit'
# Dependencies for Selenium web page testing
- test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar
build.xml
@@ -302,6 +302,7 @@
            <class name="com.gitblit.RedmineUserService" />
            <class name="com.gitblit.SalesforceUserService" />
            <class name="com.gitblit.WindowsUserService" />
            <class name="com.gitblit.PAMUserService" />
        </mx:genjar>
        <!-- Build the WAR file -->
@@ -421,6 +422,7 @@
            <class name="com.gitblit.RedmineUserService" />
            <class name="com.gitblit.SalesforceUserService" />
            <class name="com.gitblit.WindowsUserService" />
            <class name="com.gitblit.PAMUserService" />
        </mx:genjar>
        <!-- Build Express Zip file -->
gitblit.iml
@@ -468,6 +468,17 @@
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library">
      <library name="libpam4j-1.7.jar">
        <CLASSES>
          <root url="jar://$MODULE_DIR$/ext/libpam4j-1.7.jar!/" />
        </CLASSES>
        <JAVADOC />
        <SOURCES>
          <root url="jar://$MODULE_DIR$/ext/src/libpam4j-1.7.jar!/" />
        </SOURCES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="TEST">
      <library name="junit-4.11.jar">
        <CLASSES>
releases.moxie
@@ -32,9 +32,12 @@
    additions:
    - Added optional browser-side page caching using Last-Modified and Cache-Control for the dashboard, activity, project, and several repository pages
    - Added a GET_USER request type for the RPC mechanism (issue-275)
    - Added PAMUserService to authenticate against a local Linux/Unix/MacOSX server
    dependencyChanges: ~
    settings:
    - { name: 'web.pageCacheExpires', defaultValue: 0 }
    - { name: 'realm.pam.backingUserService', defaultValue: 'users.conf' }
    - { name: 'realm.pam.serviceName', defaultValue: 'system-auth' }
    contributors:
    - Rainer Alföldi 
    - Liyu Wang
src/main/distrib/data/gitblit.properties
@@ -501,6 +501,7 @@
#    com.gitblit.RedmineUserService
#    com.gitblit.SalesforceUserService
#    com.gitblit.WindowsUserService
#    com.gitblit.PAMUserService
#
# Any custom user service implementation must have a public default constructor.
#
@@ -1212,6 +1213,21 @@
# SINCE 1.3.0
realm.windows.defaultDomain =
# The PAMUserService must be backed by another user service for standard user
# and team management.
# default: users.conf
#
# RESTART REQUIRED
# BASEFOLDER
# SINCE 1.3.1
realm.pam.backingUserService = ${baseFolder}/users.conf
# The PAM service name for authentication.
# default: system-auth
#
# SINCE 1.3.1
realm.pam.serviceName = system-auth
# The SalesforceUserService must be backed by another user service for standard user
# and team management.
# default: users.conf
src/main/java/com/gitblit/Constants.java
@@ -480,7 +480,7 @@
    }
    
    public static enum AccountType {
        LOCAL, EXTERNAL, LDAP, REDMINE, SALESFORCE, WINDOWS;
        LOCAL, EXTERNAL, LDAP, REDMINE, SALESFORCE, WINDOWS, PAM;
        
        public boolean isLocal() {
            return this == LOCAL;
src/main/java/com/gitblit/PAMUserService.java
New file
@@ -0,0 +1,142 @@
/*
 * 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;
import java.io.File;
import org.jvnet.libpam.PAM;
import org.jvnet.libpam.PAMException;
import org.jvnet.libpam.impl.CLibrary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccountType;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
/**
 * Implementation of a PAM user service for Linux/Unix/MacOSX.
 *
 * @author James Moger
 */
public class PAMUserService extends GitblitUserService {
    private final Logger logger = LoggerFactory.getLogger(PAMUserService.class);
    private IStoredSettings settings;
    public PAMUserService() {
        super();
    }
    @Override
    public void setup(IStoredSettings settings) {
        this.settings = settings;
        String file = settings.getString(Keys.realm.pam.backingUserService, "${baseFolder}/users.conf");
        File realmFile = GitBlit.getFileOrFolder(file);
        serviceImpl = createUserService(realmFile);
        logger.info("PAM User Service backed by " + serviceImpl.toString());
        // Try to identify the passwd database
        String [] files = { "/etc/shadow", "/etc/master.passwd" };
        File passwdFile = null;
        for (String name : files) {
            File f = new File(name);
            if (f.exists()) {
                passwdFile = f;
                break;
            }
        }
        if (passwdFile == null) {
            logger.error("PAM User Service could not find a passwd database!");
        } else if (!passwdFile.canRead()) {
            logger.error("PAM User Service can not read passwd database {}! PAM authentications may fail!", passwdFile);
        }
    }
    @Override
    public boolean supportsCredentialChanges() {
        return false;
    }
    @Override
    public boolean supportsDisplayNameChanges() {
        return true;
    }
    @Override
    public boolean supportsEmailAddressChanges() {
        return true;
    }
    @Override
    public boolean supportsTeamMembershipChanges() {
        return true;
    }
     @Override
    protected AccountType getAccountType() {
        return AccountType.PAM;
    }
    @Override
    public UserModel authenticate(String username, char[] password) {
        if (isLocalAccount(username)) {
            // local account, bypass PAM authentication
            return super.authenticate(username, password);
        }
        if (CLibrary.libc.getpwnam(username) == null) {
            logger.warn("Can not get PAM passwd for " + username);
            return null;
        }
        PAM pam = null;
        try {
            String serviceName = settings.getString(Keys.realm.pam.serviceName, "system-auth");
            pam = new PAM(serviceName);
            pam.authenticate(username, new String(password));
        } catch (PAMException e) {
            logger.error(e.getMessage());
            return null;
        } finally {
            pam.dispose();
        }
        UserModel user = getUserModel(username);
        if (user == null)    // create user object for new authenticated user
            user = new UserModel(username.toLowerCase());
        // create a user cookie
        if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
            user.cookie = StringUtils.getSHA1(user.username + new String(password));
        }
        // update user attributes from UnixUser
        user.accountType = getAccountType();
        user.password = Constants.EXTERNAL_ACCOUNT;
        // TODO consider mapping PAM groups to teams
        // push the changes to the backing user service
        super.updateUserModel(user);
        return user;
    }
}
src/site/features.mkd
@@ -37,6 +37,7 @@
- Redmine authentication
- Salesforce.com authentication
- Windows authentication
- PAM authentication
- Gravatar integration
- Git-notes display support
- Submodule support
src/site/setup_authentication.mkd
@@ -6,6 +6,7 @@
* LDAP authentication
* Windows authentication
* PAM authentication
* Redmine auhentication
* Salesforce.com authentication
* Servlet container authentication
@@ -83,6 +84,13 @@
    realm.userService = com.gitblit.WindowsUserService
    realm.windows.defaultDomain =
### PAM Authentication
PAM authentication is based on the use of libpam4j and JNA.  To use this service, your Gitblit server must be installed on a Linux/Unix/MacOSX machine and the user that Gitblit runs-as must have root permissions.
    realm.userService = com.gitblit.PAMUserService
    realm.pam.serviceName = system-auth
### Redmine Authentication
You may authenticate your users against a Redmine installation as long as your Redmine install has properly enabled [API authentication](http://www.redmine.org/projects/redmine/wiki/Rest_Api#Authentication).  This user service only supports user authentication; it does not support team creation based on Redmine groups.  Redmine administrators will also be Gitblit administrators.
src/site/siteindex.mkd
@@ -68,9 +68,10 @@
- Groovy push hook scripts
- Pluggable user service mechanism
    - LDAP authentication with optional LDAP-controlled Team memberships
    - Redmine authentication
    - SalesForce.com authentication
    - Windows authentication
    - Redmine authentication
    - SalesForce.com authentication
    - Windows authentication
    - PAM authentication
    - Custom authentication, authorization, and user management
- Rich RSS feeds
- JSON-based RPC mechanism