package hudson.plugins.active_directory;
import com4j.COM4J;
import com4j.Com4jObject;
import com4j.ComException;
import com4j.Variant;
import com4j.typelibs.activeDirectory.IADs;
import com4j.typelibs.activeDirectory.IADsGroup;
import com4j.typelibs.activeDirectory.IADsOpenDSObject;
import com4j.typelibs.activeDirectory.IADsUser;
import com4j.typelibs.ado20.ClassFactory;
import com4j.typelibs.ado20._Command;
import com4j.typelibs.ado20._Connection;
import com4j.typelibs.ado20._Recordset;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.providers.AuthenticationProvider;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.springframework.dao.DataAccessException;
import hudson.security.GroupDetails;
import hudson.security.SecurityRealm;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* {@link AuthenticationProvider} with Active Directory, plus {@link UserDetailsService}
*
* @author Kohsuke Kawaguchi
*/
public class ActiveDirectoryAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider
implements UserDetailsService, GroupDetailsService {
private final String defaultNamingContext;
/**
* ADO connection for searching Active Directory.
*/
private final _Connection con;
public ActiveDirectoryAuthenticationProvider() {
IADs rootDSE = COM4J.getObject(IADs.class, "LDAP://RootDSE", null);
defaultNamingContext = (String)rootDSE.get("defaultNamingContext");
LOGGER.info("Active Directory domain is "+defaultNamingContext);
con = ClassFactory.createConnection();
con.provider("ADsDSOObject");
con.open("Active Directory Provider",""/*default*/,""/*default*/,-1/*default*/);
}
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
// active directory authentication is not by comparing clear text password,
// so there's nothing to do here.
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
return retrieveUser(username,null);
}
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
String password = null;
if(authentication!=null)
password = (String) authentication.getCredentials();
String dn = getDnOfUserOrGroup(username);
// now we got the DN of the user
IADsOpenDSObject dso = COM4J.getObject(IADsOpenDSObject.class,"LDAP:",null);
// turns out we don't need DN for authentication
// we can bind with the user name
// dso.openDSObject("LDAP://"+context,args[0],args[1],1);
// to do bind with DN as the user name, the flag must be 0
IADsUser usr;
try {
usr = (authentication==null
? dso.openDSObject("LDAP://"+dn, null, null, 0)
: dso.openDSObject("LDAP://"+dn, dn, password, 0))
.queryInterface(IADsUser.class);
} catch (ComException e) {
throw new BadCredentialsException("Incorrect password for "+username);
}
if (usr == null) // the user name was in fact a group
throw new UsernameNotFoundException("User not found: "+username);
List<GrantedAuthority> groups = new ArrayList<GrantedAuthority>();
for( Com4jObject g : usr.groups() ) {
IADsGroup grp = g.queryInterface(IADsGroup.class);
// cut "CN=" and make that the role name
groups.add(new GrantedAuthorityImpl(grp.name().substring(3)));
}
groups.add(SecurityRealm.AUTHENTICATED_AUTHORITY);
return new ActiveDirectoryUserDetail(
username, password,
!usr.accountDisabled(),
true, true, true,
groups.toArray(new GrantedAuthority[groups.size()])
);
}
protected String getDnOfUserOrGroup(String userOrGroupname) {
_Command cmd = ClassFactory.createCommand();
cmd.activeConnection(con);
cmd.commandText("<LDAP://"+defaultNamingContext+">;(sAMAccountName="+userOrGroupname+");distinguishedName;subTree");
_Recordset rs = cmd.execute(null, Variant.MISSING, -1/*default*/);
if(rs.eof())
throw new UsernameNotFoundException("No such user or group: "+userOrGroupname);
String dn = rs.fields().item("distinguishedName").value().toString();
return dn;
}
private static final Logger LOGGER = Logger.getLogger(ActiveDirectoryAuthenticationProvider.class.getName());
public GroupDetails loadGroupByGroupname(String groupname) {
// First get the distinguishedName
String dn = getDnOfUserOrGroup(groupname);
IADsOpenDSObject dso = COM4J.getObject(IADsOpenDSObject.class, "LDAP:",
null);
IADsGroup group = dso.openDSObject("LDAP://" + dn, null, null, 0)
.queryInterface(IADsGroup.class);
// If not a group will return null
if (group == null) {
throw new UsernameNotFoundException("Group not found: " + groupname);
}
return new ActiveDirectoryGroupDetails(groupname);
}
}