PennIDTranslation
From Provider Wiki
|
Applications or services requiring a user’s PennID for authorization decisions will be required to retrieve the PennID using the PennKey – PennID LDAP translation service (PennGroups).
Prerequisites
The service is available to any person with a PennKey, but the PennKey must be authorized. An application or entity that can use PennGroups can also use the translation service. To use the translation from a web service requires a kerberos service principal created by an administrator with kadmin privileges. Once the service principal is created, PennGroups must be configured to allow access for the service principal. As a temporary process, to register a service principal for PennGroups, please contact PennGroups Help[1].
Sample LDAP Invocations
Use the PennGroups client to test connectivity
Download the penngroups client:
http://www.upenn.edu/computing/penngroups/pennGroupsClient-1.4.2.zip
Unzip to a directory. You need java1.5+ (aka java5+) on the machine. Edit the top of the grouper.client.properties to put in your service principal and password:
######################################## ## LDAP connection settings ######################################## # url of directory, including the base DN (distinguished name) # e.g. ldap://server.school.edu/dc=school,dc=edu # e.g. ldaps://server.school.edu/dc=school,dc=edu grouperClient.ldap.url = ldaps://penngroups.upenn.edu/dc=upenn,dc=edu # kerberos principal used to connect to ldap # e.g. name/server.whatever.upenn.edu grouperClient.ldap.kerberosPrincipal = penngroups/medley.isc-seo.upenn.edu # password for shared secret authentication to ldap # or you can put a filename with an encrypted password grouperClient.ldap.password = xxxxxxx
Then try to do a conversion:
C:\temp\pennGroupsClient-1.4.2>c:\dev_inst\java\bin\java -jar grouperClient.jar --operation=pennnameToPennid --pennnameToDecode=mchyzer pennid: 10021368 C:\temp\pennGroupsClient-1.4.2>
Note, just use this command for more options: java -jar grouperClient.jar
Convert PennName to PennID
As user 'jorj', use my PennKey password to convert the pennname 'dakowitz' to a pennid:
# ldapsearch -LLL -x -H ldaps://penngroups.upenn.edu/ -b "ou=pennnames,dc=upenn,dc=edu" -D "uid=jorj,ou=entities,dc=upenn,dc=edu" -W pennname=dakowitz
Convert PennID to a PennName (I)
As user 'jorj', use my PennKey password to convert the pennid 10018604 to a name:
# ldapsearch -LLL -x -H ldaps://penngroups.upenn.edu/ -b "ou=pennnames,dc=upenn,dc=edu" -D "uid=jorj,ou=entities,dc=upenn,dc=edu" -W pennid=10018604
Convert PennID to PennName (II)
Using kerberos credentials, convert the pennid 31653243 to a pennname:
# ldapsearch -LLL -Y GSSAPI -H ldap://penngroups.upenn.edu/ -b "ou=pennnames,dc=upenn,dc=edu" pennid=31653243
Sample LDAP Invocations
Perl
Using GSSAPI (native Kerberos)
#!/usr/bin/perl
use strict;
use warnings;
use Net::LDAP;
use Authen::SASL;
my $lookup_pennname = shift (@ARGV) || die "No pennname given";
# empty callback so Net::LDAP doesn't override it
my $sasl = Authen::SASL->new('GSSAPI',
'user' => '');
my $ldap = Net::LDAP->new( 'penngroups.upenn.edu',
version => 3 )
|| die "$@";
my $mesg = $ldap->bind( '', sasl => $sasl );
$mesg->code && die $mesg->error;
$mesg = $ldap->search(
base => 'ou=pennnames,dc=upenn,dc=edu',
filter => "(&(pennname=${lookup_pennname})(objectClass=*))",
attrs => [ 'pennname', 'pennid' ],
);
$mesg->code && die $mesg->error;
foreach my $entry ( $mesg->sorted('pennname') ) {
print sprintf("%s: %s\n",
$entry->get_value('pennname'),
$entry->get_value('pennid'));
}
$ldap->unbind;
Using Username and Password
#!/usr/bin/perl
use strict;
use warnings;
use Net::LDAP;
my $lookup_pennname = shift (@ARGV) || die "No pennname given";
my $ldap = Net::LDAP->new( 'ldaps://penngroups.upenn.edu',
version => 3 )
|| die "$@";
my $mesg = $ldap->bind( 'uid=<pennname>,ou=entities,dc=upenn,dc=edu',
password => '<password>' );
$mesg->code && die $mesg->error;
$mesg = $ldap->search(
base => 'ou=pennnames,dc=upenn,dc=edu',
filter => "(&(pennname=${lookup_pennname})(objectClass=*))",
attrs => [ 'pennname', 'pennid' ],
);
$mesg->code && die $mesg->error;
foreach my $entry ( $mesg->sorted('pennname') ) {
print sprintf("%s: %s\n",
$entry->get_value('pennname'),
$entry->get_value('pennid'));
}
$ldap->unbind;
PHP
<?php
$lookup_name = 'user';
$filter = "(&(pennname= " . $lookup_name . ")(objectClass=*))";
$pg_server = 'penngroups.upenn.edu';
$bind_dn = 'uid=<authorized_pennname>,ou=entities,dc=upenn,dc=edu';
$base_dn = 'ou=pennnames,dc=upenn,dc=edu';
$attrs = array( 'pennname', 'pennid' );
$lh = ldap_connect( $pg_server );
if ( !$lh ) {
echo "ldap_connect failed: " . ldap_error( $lh );
exit( 1 );
}
ldap_set_option( $lh, LDAP_OPT_PROTOCOL_VERSION, 3 );
ldap_set_option( $lh, LDAP_OPT_REFERRALS, 0 );
if ( !ldap_start_tls( $lh )) {
echo "ldap_start_tls failed: " . ldap_error( $lh );
exit( 1 );
}
if ( !ldap_bind( $lh, $bind_dn, "<password>")) {
echo "ldap_bind failed: " . ldap_error( $lh );
exit( 1 );
}
$results = ldap_search( $lh, $base_dn, $filter, $attrs );
if ( !$results ) {
echo "ldap_search failed: " . ldap_error( $lh );
exit( 1 );
}
$entries = ldap_get_entries( $lh, $results );
if ( !$entries ) {
echo "ldap_get_entries failed: " . ldap_error( $lh );
exit( 1 );
}
for ( $i = 0; $i < $entries[ 'count' ]; $i++ ) {
$entry = $entries[ $i ];
for ( $j = 0; $j < $entry[ 'count' ]; $j++ ) {
if ( !isset( $entry[ 'pennname' ][ $j ] ) ||
!isset( $entry[ 'pennid' ][ $j ] )) {
continue;
}
echo $entry[ 'pennname' ][ $j ] . ": " .
$entry[ 'pennid' ][ $j ] . "\n";
}
}
// this closes the connection, too.
ldap_unbind( $lh );
exit( 0 );
?>
Java
import java.util.Hashtable;
import java.util.Enumeration;
import java.net.URLEncoder;
import javax.naming.*;
import javax.naming.directory.*;
public class PennkeyPennIDConverter
{
public static String initialContext = "com.sun.jndi.ldap.LdapCtxFactory";
public static String pennGroupsServer = "ldaps://penngroups.upenn.edu";
public static String searchBase = "ou=pennnames,dc=upenn,dc=edu";
public static String bindDN = "uid=<AUTHORIZED USERNAME>,ou=entities,dc=upenn,dc=edu";
public static String bindPassword = "password";
public static void main( String args[] )
{
try {
Hashtable<String,String> env = new Hashtable<String,String>();
String lookupUser;
String filter;
DirContext pennGroupsContext;
SearchControls controls;
NamingEnumeration results;
lookupUser = URLEncoder.encode( args[0], "UTF-8" );
filter = "(&(pennname=" + lookupUser + ")(objectClass=*))";
env.put( Context.INITIAL_CONTEXT_FACTORY, initialContext );
env.put( Context.PROVIDER_URL, pennGroupsServer );
env.put( Context.SECURITY_AUTHENTICATION, "simple" );
env.put( Context.SECURITY_PRINCIPAL, bindDN );
env.put( Context.SECURITY_CREDENTIALS, bindPassword );
pennGroupsContext = new InitialDirContext( env );
controls = new SearchControls();
controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
controls.setReturningAttributes( new String[] { "pennname",
"pennid" } );
results = pennGroupsContext.search( searchBase, filter, controls );
while ( results != null && results.hasMore()) {
SearchResult r = (SearchResult)results.next();
Attributes attrs = r.getAttributes();
String pennname, pennid;
pennname = (String)attrs.get( "pennname" ).get();
pennid = (String)attrs.get( "pennid" ).get();
System.out.println( pennname + ": " + pennid );
}
} catch ( Exception e ) {
e.printStackTrace();
System.exit( 1 );
}
System.exit( 0 );
}
}
ColdFusion
Download the PennGroups.doc file (Media:PennGroups.doc) and copy the code making a PennGroups.cfc. You will instantiate an object using your Kerberos Principal and Password. Results will be limited to your account's access level. Below is example code to setup and execute actions against the cfc.
<cfset localStruct.CFC_PATH ="PATH_TO_PENNGROUPS" />
<cfset localStruct.k_username ="YOUR_KERBEROS_PRINCIPAL" />
<cfset localStruct.k_password ="YOUR_KERBEROS_PASSWORD" />
<!--- a valid non Temp Employee --->
<cfset localStruct.penn_id="YOUR_PENN_ID"/>
<cfset localStruct.pennName="YOUR_PENNNAME"/>
<cfset PennGroupsObj = CreateObject("component", "#localStruct.CFC_PATH#").Init(ldapUser="#localStruct.k_username#",
ldapPassword="#localStruct.k_password#")
/>
<cfsetting requesttimeout="180" >
<cfoutput>
<h2>Lookup Penn_Id</h2>
<p>Penn_Id should be #localStruct.penn_id#:
<cfdump var="#PennGroupsObj.getPenn_ID(pennName='#localStruct.pennName#')#" />
</p>
<p>Using invalid PennName, Penn_Id should be Empty String:
<cfdump var="#PennGroupsObj.getPenn_ID(pennName='adfasdf#localStruct.pennName#123123')#" />
</p>
<h2>Lookup PennName</h2>
<p>PennName should be #localStruct.pennName#:
<cfdump var="#PennGroupsObj.getPennName(penn_id='#localStruct.penn_id#')#" />
</p>
<h2>Is #localStruct.pennName# a member of the group employeeNonTemp</h2>
<p>isMember True of cn=penn:community:employeeNonTemp :
<cfdump var="#PennGroupsObj.isMember(pennName='#localStruct.pennName#',groupName='cn=penn:community:employeeNonTemp')#" />
</p>
<p>isMember False of cn=penn:community:employeeNonTemp :
<cfdump var="#PennGroupsObj.isMember(pennName='adsfadsf#localStruct.pennName#123423423',
groupName='cn=penn:community:employeeNonTemp')#"
/>
</p>
<h2>Get all members of employeeNonTemp</h2>
<p>getMembers of cn=penn:community:employeeNonTemp :
<cfdump var="#PennGroupsObj.getMembers(groupName='cn=penn:community:employeeNonTemp')#" />
</p>
<h2>Get all Groups #localStruct.k_username# can see</h2>
<p>ListAllGroups in Groups OU :
<cfdump var="#PennGroupsObj.getAllGroups()#" />
</p>
<h2>Get all groups by a PennName</h2>
<p>GetAllGroupsByMember using #localStruct.pennName# :
<cfdump var="#PennGroupsObj.getAllGroupsByMember(pennName='#localStruct.pennName#')#" />
</p>
<p>GetAllGroupsByMember invalid PennName, :
<cfdump var="#PennGroupsObj.getAllGroupsByMember(pennName='123123#localStruct.pennName#123123')#" />
</p>
</cfoutput>
ASP.NET (C#)
<%@ Page Language="C#" %>
<%@ assembly name="System.DirectoryServices, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>
<%@ import Namespace="System.DirectoryServices" %>
<%@ import Namespace="System.Text.RegularExpressions" %>
<script runat="server">
/*
Change ldapUsername and ldapPassword to match
your registered Kerberos principal and password
*/
string ldapUsername = ""; //Example: ldapUsername = “uid=myPennName,ou=entities,dc=upenn,dc=edu”;
string ldapPassword = "";
// DO NOT MAKE CHANGES BELOW THIS LINE
string pennKey = "[PennKey not available in REMOTE_USER header.]";
string pennId = "[Not retrieved.]";
void Page_Load(Object sender, EventArgs args) {
if ( !isNullOrEmpty(Request.Headers["REMOTE_USER"]) && !isNullOrEmpty(Request.Headers["COSIGN_SERVICE"]) ) {
pennKey = Request.Headers["REMOTE_USER"];
if ( !Regex.IsMatch(pennKey, @"^[A-Za-z][A-Za-z0-9]{1,7}$") ) {
pennId = "[PennID not retrieved. PennKey unsafe for use in LDAP query.]";
return;
}
if ( isNullOrEmpty (ldapUsername) || isNullOrEmpty(ldapPassword) ) {
pennId = "[PennID not retrieved. LDAP credentials not set.]";
return;
}
try {
DirectoryEntry entry = new DirectoryEntry("LDAP://penngroups.upenn.edu/ou=pennnames,dc=upenn,dc=edu");
entry.AuthenticationType = AuthenticationTypes.Encryption;
entry.Username = ldapUsername;
entry.Password = ldapPassword;
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "pennname=" + Request.Headers["REMOTE_USER"];
SearchResult result = searcher.FindOne();
if (result != null ) {
ResultPropertyCollection coll = result.Properties;
pennId = result.Properties["pennid"][0].ToString();
}
}
catch (Exception e) {
pennId = "[Exception occurred during retrieval. LDAP username and password likely unset.]";
}
}
}
bool isNullOrEmpty ( string str ) {
return (str == null || str.Length == 0);
}
</script>
<html>
<head>
<title></title>
</head>
<body>
<p>
<%
Response.Write("PennKey: " + Server.HtmlEncode( pennKey ) + "<br/>");
Response.Write("PennID: " + Server.HtmlEncode( pennId ) );
%>
</p>
</body>
</html>
Additional Information on PennGroups
http://prowiki.isc.upenn.edu/wiki/PennGroups
The Pennkey to pennid translation currently updates data once per day. Note this is scheduled to be fixed in March 2010. Until then, please use the penngroups web service to translate pennkeys to pennids if ldap doesnt have the information. PennGroups will soon update once per hour. You can do that command line like this (this is an example, setup a group with no members that your principal can READ):
[mchyzer@flash pennGroupsClient-1.4.2]$ java -jar grouperClient.jar --operation=hasMemberWs --groupName=penn:uphs:apps:pennNameToPennId:pennNameToPennId --pennKeys=mchyzer --outputTemplate='${wsSubject.id}$newline$' --sourceIds=pennperson
10021368
[mchyzer@flash pennGroupsClient-1.4.2]$
If you are using web service, it looks like this:
[mchyzer@flash pennGroupsClient-1.4.2]$ java -jar grouperClient.jar --operation=hasMemberWs --groupName=penn:uphs:apps:pennNameToPennId:pennNameToPennId --pennKeys=mchyzer --outputTemplate='${wsSubject.id}$newline$' --sourceIds=pennperson --debug=true
DEBUG: Reading resource: grouper.client.properties, from: /home/mchyzer/grouper/pennGroupsClient-1.4.2/grouper.client.properties
DEBUG: WebService: connecting as user: 'penngroups/medley.isc-seo.upenn.edu'
DEBUG: WebService: connecting to URL: 'https://medley10.isc-seo.upenn.edu/grouperWs/servicesRest/v1_4_000/groups/penn%3Auphs%3Aapps%3ApennNameToPennId%3ApennNameToPennId/members'
################ REQUEST START (indented) ###############
POST /grouperWs/servicesRest/v1_4_000/groups/penn:uphs:apps:pennNameToPennId:pennNameToPennId/members HTTP/1.1
Connection: close
Authorization: Basic xxxxxxxxxxxxxxxx
User-Agent: Jakarta Commons-HttpClient/3.1
Host: medley10.isc-seo.upenn.edu:-1
Content-Length: 310
Content-Type: text/xml; charset=UTF-8
<WsRestHasMemberRequest>
<wsGroupLookup>
<groupName>penn:uphs:apps:pennNameToPennId:pennNameToPennId</groupName>
</wsGroupLookup>
<subjectLookups>
<WsSubjectLookup>
<subjectIdentifier>mchyzer</subjectIdentifier>
<subjectSourceId>pennperson</subjectSourceId>
</WsSubjectLookup>
</subjectLookups>
</WsRestHasMemberRequest>
################ REQUEST END ###############
################ RESPONSE START (indented) ###############
HTTP/1.1 200 OK
Date: Wed, 23 Dec 2009 13:28:06 GMT
Server: Apache/2.2.13 (Unix) DAV/2 PHP/5.2.11 mod_jk/1.2.28 mod_ssl/2.2.13 OpenSSL/0.9.8e-fips-rhel5
Set-Cookie: JSESSIONID=xxxxxxxxxxxx; Secure
X-Grouper-resultCode: SUCCESS
X-Grouper-success: T
X-Grouper-resultCode2: NONE
Content-Length: 1378
Connection: close
Content-Type: text/xml
<WsHasMemberResults>
<results>
<WsHasMemberResult>
<wsSubject>
<identifierLookup>mchyzer</identifierLookup>
<resultCode>SUCCESS</resultCode>
<success>T</success>
<id>10021368</id>
<sourceId>pennperson</sourceId>
</wsSubject>
<resultMetadata>
<resultCode>IS_NOT_MEMBER</resultCode>
<success>T</success>
</resultMetadata>
</WsHasMemberResult>
</results>
<wsGroup>
<extension>pennNameToPennId</extension>
<displayExtension>pennNameToPennId</displayExtension>
<description>This group contains no members, but can be queried to get results which can translate pennkey/pennid</description>
<displayName>penn:uphs:apps:pennNameToPennId:pennNameToPennId</displayName>
<name>penn:uphs:apps:pennNameToPennId:pennNameToPennId</name>
<uuid>db38d4e84404428a93fac3c528271f95</uuid>
</wsGroup>
<resultMetadata>
<resultCode>SUCCESS</resultCode>
<resultMessage>Success for: clientVersion: v1_4_000, wsGroupLookup: WsGroupLookup[groupName=penn:uphs:apps:pennNameToPennId:pennNameToPennId], subjectLookups: Array size: 1: [0]: WsSubjectLookup[subjectIdentifier=mchyzer,subjectSourceId=pennperson]
memberFilter: All, actAsSubject: null, fieldName: null, includeGroupDetail: false, includeSubjectDetail: false, subjectAttributeNames: null
,params: null
</resultMessage>
<success>T</success>
</resultMetadata>
<responseMetadata>
<millis>195</millis>
<serverVersion>v1_4_002</serverVersion>
</responseMetadata>
</WsHasMemberResults>
################ RESPONSE END ###############
DEBUG: Output template: ${wsSubject.id}
, available variables: wsHasMemberResults, grouperClientUtils, index, wsGroup, wsHasMemberResult, wsSubject, resultMetadata, hasMember
10021368
DEBUG: Elapsed time: 1918ms
[mchyzer@flash pennGroupsClient-1.4.2]$
