Jared Housh and Clark Trimble
The University of Tulsa
Box
Shibboleth
Directory
Grouper
Identity Vault
ERP
//attribute-filter.xml
<afp:AttributeFilterPolicy id="box">
<afp:PolicyRequirementRule xsi:type="basic:AttributeRequesterString"
value="box.net" />
<afp:AttributeRule attributeID="ldgroup">
<afp:PermitValueRule xsi:type="basic:ANY" />
<afp:AttributeRule>
<afp:AttributeFilterPolicy>
//attribute-resolver.xml
<resolver:AttributeDefinition xsi:type="ad:Script" id="ldgroup">
<resolver:Dependency ref="myLDAP" />
<resolver:AttributeEncoder xsi:type="enc:SAML1String"
name="urn:mace:dir:attribute-def:group" />
<resolver:AttributeEncoder xsi:type="enc:SAML2String"
name="urn:oid:0.9.2342.19200300.100.1.4" friendlyName="group" />
<ad:Script><CDATA[ js-is-good-for-you! ]]><ad:Script>
<resolver:AttributeDefinition>
// js-is-good-for-you!
importPackage(Packages.edu.internet2.middleware.shibboleth.common.attribute.provider);
importPackage(Packages.org.slf4j);
logger = LoggerFactory.getLogger("edu.internet2.middleware.shibboleth.resolver.Script.scriptTest");
ldgroup = new BasicAttribute("ldgroup");
if (typeof memberOf != "undefined" && memberOf != null ) {
for ( i = 0; memberOf != null && i < memberOf.getValues().size(); i++ ) {
dn = memberOf.getValues().get(i);
if ( dn.match(/,ou=groupergroups,o=utulsa.edu$/) ) {
parts = dn.split(",").slice(0,-2);
for ( j=0; j < parts.length; j++ ) {
parts[j] = parts[j].split("=")[1];
}
path = parts.reverse().join(":");
ldgroup.getValues().add(path);
}
}
}
logger.info("LDGROUP: "+ldgroup.getValues());
class Entry
def self.set_particulars(particulars)
def self.auth(dn,pw)
def self.connect(dn,pw)
def self.dir
def dir
def self.find(dn)
def self.search(base,filter)
def operation(attr,value=nil)
def set(struct)
module LdapConfig
module AdConfig
class LdapEntry < Entry
include LdapConfig
def self.encode(pw)
class AdEntry < Entry
include AdConfig
def self.encode(pw)
def operation(attr,value=nil)
Think about adding detail for config, in particular, usage by base class methods.
Think about adding attrfrump/moop slide.
module Person
module ClassMethods
def find(id)
def search(filter,attrs=nil)
def uid_to_dn(uid)
def create(uid,did,first,last)
class PersonLdapEntry < LdapEntry
include Person
def self.generate_uid_number
def add_mail_attributes
def generate_primary_mail
def self.lookup(id)
class PersonAdEntry < AdEntry
include Person
module Group
module ClassMethods
def path_to_dn(path)
def dn_to_path(dn)
class GroupLdapEntry < LdapEntry
include Group
def self.groups
class GroupAdEntry < AdEntry
include Group
def initialize(keyvals={})
def flatten_ranged_attribute(attribute)
def self.membership(dn)
Flexible w/o duplicate code :)
*not synchronized to directory
select tuid as SUBJECT_ID
from namevalue
where name = 'employment_status_code'
and value = 'R'
mirror of synchronized structure
automated group is member in front group
individial exceptions added to front group
Classic use case: CIO not in IT!
For example:
composite:law-faculty = employee:organization:law ∩ employee:faculty
employee:law-faculty = composite:law-faculty ∪ additional members
// a work in progress !!
def create_group(path)
name = path.split(':').last
req = Net::HTTP::Post.new "/grouper-ws/servicesRest/v2.0.1/groups"
req.body = { "WsRestGroupSaveRequest" => {
"wsGroupToSaves" => [ {
"wsGroup" => {
"name" => path,
"extension" => name,
"detail" => { "typeNames" => ["utulsaGroup"] },
},
"wsGroupLookup" => { "groupName" => path }
} ]
}}.to_json
req.add_field 'Content-type', 'text/x-json'
req.basic_auth @username, @password
r = JSON.parse(@http.request(req).body)['WsGroupSaveResults']
unless r['resultMetadata']['resultCode'] == 'SUCCESS'
raise "cannot create group:::#{r}"
end
end
To use event-driven change notification:
# grouper-loader.properties
changeLog.consumer.httpTestGroup.class = edu.internet2.middleware.grouper.changeLog.esb.consumer.EsbConsumer
changeLog.consumer.httpTestGroup.publisher.class = edu.internet2.middleware.grouper.changeLog.esb.consumer.EsbHttpPublisher
changeLog.consumer.httpTestGroup.publisher.url = http://server.utulsa.edu:4499/
changeLog.consumer.httpTestGroup.quartzCron = 48 * * * * ?
Posts JSON to the listening server
create view subject as select b.tuid as id,
(select a.value from namevalue as a
where a.name='first' and a.tuid=b.tuid) as first,
(select a.value from namevalue as a
where a.name='last' and a.tuid=b.tuid) as last,
(select a.value from namevalue as a
where a.name='uid' and a.tuid=b.tuid) as uid,
(select a.value from namevalue as a
where a.name='displayname' and a.tuid=b.tuid) as displayname,
(select lower(a.value) from namevalue as a
where a.name='displayname' and a.tuid=b.tuid) as lowername
from
(select distinct tuid from namevalue) as b ;
Lookup within Grouper is stubbornly slow. :(
Turn namevalue pairs in to something like a more typical table. Key element is from clause. Everything builds from there. We've tried: different formulations, indexing, materializing the view.Run queries against data warehouse:
queries = [
{ mapper: [
{ name:'tuid', value:'HRPER_ID' },
{ name:'office_building', value:'BUILDING_DESC' },
{ name:'office_room', value:'HRP_PRI_CAMPUS_OFFICE' },
{ name:'office_extension', value:'HRP_PRI_CAMPUS_EXTENSION' },
],
tables: ['dbo.ODS_HRPER'],
where:"where HRP_EFFECT_TERM_DATE > GETDATE()" },
...
Questions?
Comments?
jared-housh@utulsa.edu patrick-trimble@utulsa.edu