This guide is intended to give you some ideas about how to generate an eduPersonPrincipal (ePPN) for users.
eduPerson
eduPersonPrincipalName is defined in eduPerson 1.0, OID:1.3.6.1.4.1.5923.1.1.1.6
A scoped identifier for a person. It should be represented in the form "user@scope" where 'user' is a name-based identifier for the person and where the "scope" portion MUST be the administrative domain of the identity system where the identifier was created and assigned. Each value of 'scope' defines a namespace within which the assigned identifiers MUST be unique. Given this rule, if two eduPersonPrincipalName (ePPN) values are the same at a given point in time, they refer to the same person. There must be one and only one "@" sign in valid values of eduPersonPrincipalName.
ePPN
In Skolfederation ePPN must be:
properly scoped with a domain name owned by the Member Organization
uniquely represents a single user
never reassigned
One way to achieve this is for example to use a Base36 number (alphanumeric) that is stored as a string, padding the string left with zeros for sorting. ePPN is incremented for every new user. If we use a length of 6 (36^6), that gives us 2176782336 ePPN's before overflow occurs. Either append the scope, a owned domain, before storing the ePPN or do it later e.g., in the IdP.
Example for AD
Below is a sample script for Active Directory. The script assigns ePPN to users where the attribute is empty. The script check the AD for the highest assigned ePPN, increments it, and then assign the new ePPN. This script can be run manually or as a scheduled job. Do not execute more than one instance of the script at a time for the same SearchBase to avoid a race condition
param ( [Parameter( Mandatory, HelpMessage="The name of the attribute that holds the ePPN" )] [string]$Attribute, [Parameter( Mandatory, HelpMessage="Specifies an Active Directory path to search under" )] [ValidateScript({[adsi]::Exists("LDAP://$_")})] [string]$SearchBase, [Parameter( Mandatory, HelpMessage="The length of the ePPN i.e, EppnLen 3, will generate ePPN's from 001 to zzz" )] [int]$EppnLen, [Parameter( HelpMessage='If set, the value "@$Scope" will be appended to the ePPN' )] [string]$Scope ) $DebugPreference = "Continue" function IncStr { [CmdletBinding()] param ([string]$eppnPre="") $alphabet = "0123456789abcdefghijklmnopqrstuvwxyz" $alen = $alphabet.Length - 1 $incremented = "" foreach ($i in $eppnPre.tolower()[$eppnPre.length..0]) { $count++ $index = $alphabet.IndexOf($i) if ($alphabet.IndexOf($i) -eq $alen) { $index = 0 } else { $incremented = $eppnPre.Substring(0, $eppnPre.Length - $count) + $alphabet.Substring($index +1 ,1) + $incremented break } $incremented = $alphabet.Substring($index,1)+$incremented if ($count -eq $eppnPre.length) { $incremented = $alphabet.Substring(1,1)+$incremented } } $incremented } $newUsers = Get-ADUser -Filter {-not($Attribute -like "*")} -Properties $Attribute -SearchBase $SearchBase -ErrorAction Stop if ($newUsers) { $users = Get-ADUser -Filter '$Attribute -like "*"' -Properties $Attribute | Select-Object $Attribute $eppn = "0" $users | ForEach-Object { $prefix = ($_.$Attribute -split "@")[0] if ($prefix -gt $eppn) { $eppn = $prefix } } $newUsers | ForEach-Object { $eppn = (IncStr -eppnPre $eppn).PadLeft($EppnLen,'0') if ($eppn.Length -gt $EppnLen){ throw "ePPN lenght overflows EppnLen: $EppnLen" } if ($Scope) { $params= @{"Identity" = $_ $Attribute = "$eppn@$Scope"} } else { $params= @{"Identity" = $_ $Attribute = $eppn} } Set-ADUser @params Write-Debug "Added $eppn" } Write-Debug "New Users added" }
Parameters
Attribute <String>
REQUIRED. The name of the attribute that holds the ePPNSearchBase <String>
REQUIRED. Specifies an Active Directory path to search underEppnLen <Int>
REQUIRED. The length of the ePPN i.e, EppnLen 3, will generate ePPN's from 001 to zzzScope <String>
OPTIONAL. If set, the value "@$Scope" will be appended to the ePPN
Execution
Exampel of how to execute the script.
ad-eppn.ps1 -Attribute eduPersonPrincipalName -SearchBase "OU=Pupils,DC=example,DC=com" -EppnLen 6 -Scope exampel.com
Messurment
I measured the time it took to run scripts on a SearchBase with 100,000 users. The test was run on a computer with rather low performance and took 28 min.
Measure-Command {ad-eppn.ps1 -Attribute eduPersonPrincipalName -SearchBase "OU=Pupils,DC=example,DC=com" -EppnLen 6 -Scope exampel.com}
Days : 0
Hours : 0
Minutes : 27
Seconds : 41
Milliseconds : 812
Ticks : 16618126324
TotalDays : 0.0192339425046296
TotalHours : 0.461614620111111
TotalMinutes : 27.6968772066667
TotalSeconds : 1661.8126324
TotalMilliseconds : 1661812.6324