There are 5 comments.
 
 
OpenId Connect / cid:jira-generated-image-avatar-6ff6dfe9-9564-4006-8699-18dd21aedddf OIDC-96 Open

Mapping users to existing users

 
View issue   ·   Add comment
 

5 comments

 
cid:jira-generated-image-avatar-39f467d7-e410-4f81-823d-604bae70da61 Patrick Koska on 04/Nov/26 11:20
 
Migration is usually a one-time operation. This probably is not worth a dedicated feature since setups and prior authentication methods might vary significantly. IMHO a script is the most flexible and easiest way to go.

The current implementation of OpenID Connect Authenticator only adds XWiki.OIDC.UserClass when creating new users. As far as I can tell it always uses the profile where the correspoding XWiki.OIDC.UserClass exists from then on. So basically you need to add an XWiki.OIDC.UserClass object to your existing LDAP users (or whatever
method of authentication you've been using so far).

I solved it by parsing for users with an XWiki.LDAPProfileClass object and adding an XWiki.OIDC.UserClass object to their profiles. Here is the script I used:

Hint: Do steps 2–4 before users start logging in through OIDC, so no new duplicates appear in between
# Back up the database
# Fill in {{ISSUER}} and {{{}CSV{}}}, then view the page with {{{}DRY_RUN = true{}}}.
# Fix any red rows: missing UPNs, or conflicts with profiles from earlier test logins
# Set {{DRY_RUN = false}} and view the page once more.
# Set the OIDC configuration according to the format of your usernames, then restart: oidc.user.subjectFormater=${oidc.idtoken.upn._lowerCase}
# Delete duplicate profiles after checking they hold nothing
# If you use the Microsoft Entra v2.0 endpoint, make sure {{upn}} is in the ID token: add it as an optional claim under token configuration in the app registration

{code:java}
{{groovy}}
import org.xwiki.model.reference.LocalDocumentReference
def DRY_RUN = true
// Exactly the "iss" claim of the ID token (copy it from an OIDC-created profile)
def ISSUER = 'https://login.microsoftonline.com/<tenant-id>/v2.0'
// Contents of upn.csv: <LDAP uid>;<userPrincipalName>
def CSV = '''
user;user@example.com
'''
def upnByUid = [:]
CSV.trim().eachLine { line ->
  def parts = line.split(';')
  if (parts.length == 2) {
    upnByUid[parts[0].trim().toLowerCase()] = parts[1].trim().toLowerCase()
  }
}
def oidcClass = new LocalDocumentReference(['XWiki', 'OIDC'], 'UserClass')
def context = xcontext.context
def wiki = context.wiki
def linkedSubjects = [:]
services.query.xwql('select oidc.subject, doc.fullName from Document doc, doc.object(XWiki.OIDC.UserClass) as oidc where oidc.issuer = :issuer')
  .bindValue('issuer', ISSUER).execute().each { linkedSubjects[it[0]] = it[1] }
def profiles = services.query.xwql('select doc.fullName, ldap.uid from Document doc, doc.object(XWiki.LDAPProfileClass) as ldap order by doc.fullName').execute()
println "|=Profile|=LDAP uid|=Subject|=Result"
profiles.each { row ->
  def (fullName, uid) = [row[0], row[1]]
  def upn = uid ? upnByUid[uid.toLowerCase()] : null
  def doc = wiki.getDocument(services.model.resolveDocument(fullName), context)
  def result
  if (doc.getXObject(oidcClass) != null) {
    result = 'skipped: already linked'
  } else if (upn == null) {
    result = '(% style="color:red" %)skipped: no UPN in CSV'
  } else if (linkedSubjects.containsKey(upn)) {
    result = "(% style=\"color:red\" %)conflict: subject already on ${linkedSubjects[upn]}"
  } else if (DRY_RUN) {
    result = 'would link'
  } else {
    doc = doc.clone()
    def obj = doc.newXObject(oidcClass, context)
    obj.setStringValue('issuer', ISSUER)
    obj.setStringValue('subject', upn)
    wiki.saveDocument(doc, 'Link LDAP profile to OIDC identity', true, context)
    result = 'linked'
  }
  println "|[[${fullName}]]|${uid}|${upn ?: ''}|${result}"
}
{{/groovy}}{code}
 
cid:jira-generated-image-avatar-39f467d7-e410-4f81-823d-604bae70da61 Patrick Koska on 04/Nov/26 11:20
 
Migration is usually a one-time operation. This probably is not worth a dedicated feature since setups and prior authentication methods might vary significantly. IMHO a script is the most flexible and easiest way to go.

The current implementation of OpenID Connect Authenticator only adds XWiki.OIDC.UserClass when creating new users. As far as I can tell it always uses the profile where the correspoding XWiki.OIDC.UserClass exists from then on. So basically you need to add an XWiki.OIDC.UserClass object to your existing LDAP users (or whatever
method of authentication you've been using so far).

I solved it by parsing for users with an XWiki.LDAPProfileClass object and adding an XWiki.OIDC.UserClass object to their profiles. Here is the script I used:

Hint: Do steps 2–4 before users start logging in through OIDC, so no new duplicates appear in between
# Back up the database
# Fill in {{ISSUER}} and {{{}CSV{}}}, then view the page with {{{}DRY_RUN = true{}}}.
# Fix any red rows: missing UPNs, or conflicts with profiles from earlier test logins
# Set {{DRY_RUN = false}} and view the page once more.
# Set the OIDC configuration according to the format of your usernames, then restart: oidc.user.subjectFormater=${oidc.idtoken.upn._lowerCase}
# Delete duplicate profiles after checking they hold nothing
# If you use the Microsoft Entra v2.0 endpoint, make sure {{upn}} is in the ID token: add it as an optional claim under token configuration in the app registration

{code:java}
{{groovy}}
import org.xwiki.model.reference.LocalDocumentReference
def DRY_RUN = true
// Exactly the "iss" claim of the ID token (copy it from an OIDC-created profile)
def ISSUER = 'https://login.microsoftonline.com/<tenant-id>/v2.0'
// Contents of upn.csv: <LDAP uid>;<userPrincipalName>
def CSV = '''
user;user@example.com
'''
def upnByUid = [:]
CSV.trim().eachLine { line ->
  def parts = line.split(';')
  if (parts.length == 2) {
    upnByUid[parts[0].trim().toLowerCase()] = parts[1].trim().toLowerCase()
  }
}
def oidcClass = new LocalDocumentReference(['XWiki', 'OIDC'], 'UserClass')
def context = xcontext.context
def wiki = context.wiki
def linkedSubjects = [:]
services.query.xwql('select oidc.subject, doc.fullName from Document doc, doc.object(XWiki.OIDC.UserClass) as oidc where oidc.issuer = :issuer')
  .bindValue('issuer', ISSUER).execute().each { linkedSubjects[it[0]] = it[1] }
def profiles = services.query.xwql('select doc.fullName, ldap.uid from Document doc, doc.object(XWiki.LDAPProfileClass) as ldap order by doc.fullName').execute()
println "|=Profile|=LDAP uid|=Subject|=Result"
profiles.each { row ->
  def (fullName, uid) = [row[0], row[1]]
  def upn = uid ? upnByUid[uid.toLowerCase()] : null
  def doc = wiki.getDocument(services.model.resolveDocument(fullName), context)
  def result
  if (doc.getXObject(oidcClass) != null) {
    result = 'skipped: already linked'
  } else if (upn == null) {
    result = '(% style="color:red" %)skipped: no UPN in CSV'
  } else if (linkedSubjects.containsKey(upn)) {
    result = "(% style=\"color:red\" %)conflict: subject already on ${linkedSubjects[upn]}"
  } else if (DRY_RUN) {
    result = 'would link'
  } else {
    doc = doc.clone()
    def obj = doc.newXObject(oidcClass, context)
    obj.setStringValue('issuer', ISSUER)
    obj.setStringValue('subject', upn)
    wiki.saveDocument(doc, 'Link LDAP profile to OIDC identity', true, context)
    result = 'linked'
  }
  println "|[[${fullName}]]|${uid}|${upn ?: ''}|${result}"
}
{{/groovy}}{code}
 
cid:jira-generated-image-avatar-39f467d7-e410-4f81-823d-604bae70da61 Patrick Koska on 04/Nov/26 11:21
 
Migration is usually a one-time operation. This probably is not worth a dedicated feature since setups and prior authentication methods might vary significantly. IMHO a script is the most flexible and easiest way to go.

The current implementation of OpenID Connect Authenticator only adds XWiki.OIDC.UserClass when creating new users. As far as I can tell it always uses the profile where the correspoding XWiki.OIDC.UserClass exists from then on. So basically you need to add an XWiki.OIDC.UserClass object to your existing LDAP users (or whatever
authentication auth method you've been using so far).

I solved it by parsing for users with an XWiki.LDAPProfileClass object and adding an XWiki.OIDC.UserClass object to their profiles. Here is the script I used:

Hint: Do steps 2–4 before users start logging in through OIDC, so no new duplicates appear in between
# Back up the database
# Fill in {{ISSUER}} and {{{}CSV{}}}, then view the page with {{{}DRY_RUN = true{}}}.
# Fix any red rows: missing UPNs, or conflicts with profiles from earlier test logins
# Set {{DRY_RUN = false}} and view the page once more.
# Set the OIDC configuration according to the format of your usernames, then restart: oidc.user.subjectFormater=${oidc.idtoken.upn._lowerCase}
# Delete duplicate profiles after checking they hold nothing
# If you use the Microsoft Entra v2.0 endpoint, make sure {{upn}} is in the ID token: add it as an optional claim under token configuration in the app registration

{code:java}
{{groovy}}
import org.xwiki.model.reference.LocalDocumentReference
def DRY_RUN = true
// Exactly the "iss" claim of the ID token (copy it from an OIDC-created profile)
def ISSUER = 'https://login.microsoftonline.com/<tenant-id>/v2.0'
// Contents of upn.csv: <LDAP uid>;<userPrincipalName>
def CSV = '''
user;user@example.com
'''
def upnByUid = [:]
CSV.trim().eachLine { line ->
  def parts = line.split(';')
  if (parts.length == 2) {
    upnByUid[parts[0].trim().toLowerCase()] = parts[1].trim().toLowerCase()
  }
}
def oidcClass = new LocalDocumentReference(['XWiki', 'OIDC'], 'UserClass')
def context = xcontext.context
def wiki = context.wiki
def linkedSubjects = [:]
services.query.xwql('select oidc.subject, doc.fullName from Document doc, doc.object(XWiki.OIDC.UserClass) as oidc where oidc.issuer = :issuer')
  .bindValue('issuer', ISSUER).execute().each { linkedSubjects[it[0]] = it[1] }
def profiles = services.query.xwql('select doc.fullName, ldap.uid from Document doc, doc.object(XWiki.LDAPProfileClass) as ldap order by doc.fullName').execute()
println "|=Profile|=LDAP uid|=Subject|=Result"
profiles.each { row ->
  def (fullName, uid) = [row[0], row[1]]
  def upn = uid ? upnByUid[uid.toLowerCase()] : null
  def doc = wiki.getDocument(services.model.resolveDocument(fullName), context)
  def result
  if (doc.getXObject(oidcClass) != null) {
    result = 'skipped: already linked'
  } else if (upn == null) {
    result = '(% style="color:red" %)skipped: no UPN in CSV'
  } else if (linkedSubjects.containsKey(upn)) {
    result = "(% style=\"color:red\" %)conflict: subject already on ${linkedSubjects[upn]}"
  } else if (DRY_RUN) {
    result = 'would link'
  } else {
    doc = doc.clone()
    def obj = doc.newXObject(oidcClass, context)
    obj.setStringValue('issuer', ISSUER)
    obj.setStringValue('subject', upn)
    wiki.saveDocument(doc, 'Link LDAP profile to OIDC identity', true, context)
    result = 'linked'
  }
  println "|[[${fullName}]]|${uid}|${upn ?: ''}|${result}"
}
{{/groovy}}{code}
 
cid:jira-generated-image-avatar-39f467d7-e410-4f81-823d-604bae70da61 Patrick Koska on 04/Nov/26 11:22
 
Migration is usually a one-time operation. This probably is not worth a dedicated feature since setups and prior authentication methods might vary significantly. IMHO a script is the most flexible and easiest way to go.

The current implementation of OpenID Connect Authenticator only adds XWiki.OIDC.UserClass when creating new users. As far as I can tell it always uses the profile where the correspoding XWiki.OIDC.UserClass exists from then on. So basically you need to add an XWiki.OIDC.UserClass object to your existing LDAP users (or whatever auth method you've been using so far).

I solved it by parsing for users with an XWiki.LDAPProfileClass object and adding an XWiki.OIDC.UserClass object to their profiles.
Here is Below you'll find the script I used : .

Hint: Do steps 2–4 before users start logging in through OIDC, so no new duplicates appear in between
# Back up the database
# Fill in {{ISSUER}} and {{{}CSV{}}}, then view the page with {{{}DRY_RUN = true{}}}.
# Fix any red rows: missing UPNs, or conflicts with profiles from earlier test logins
# Set {{DRY_RUN = false}} and view the page once more.
# Set the OIDC configuration according to the format of your usernames, then restart: oidc.user.subjectFormater=${oidc.idtoken.upn._lowerCase}
# Delete duplicate profiles after checking they hold nothing
# If you use the Microsoft Entra v2.0 endpoint, make sure {{upn}} is in the ID token: add it as an optional claim under token configuration in the app registration

{code:java}
{{groovy}}
import org.xwiki.model.reference.LocalDocumentReference
def DRY_RUN = true
// Exactly the "iss" claim of the ID token (copy it from an OIDC-created profile)
def ISSUER = 'https://login.microsoftonline.com/<tenant-id>/v2.0'
// Contents of upn.csv: <LDAP uid>;<userPrincipalName>
def CSV = '''
user;user@example.com
'''
def upnByUid = [:]
CSV.trim().eachLine { line ->
  def parts = line.split(';')
  if (parts.length == 2) {
    upnByUid[parts[0].trim().toLowerCase()] = parts[1].trim().toLowerCase()
  }
}
def oidcClass = new LocalDocumentReference(['XWiki', 'OIDC'], 'UserClass')
def context = xcontext.context
def wiki = context.wiki
def linkedSubjects = [:]
services.query.xwql('select oidc.subject, doc.fullName from Document doc, doc.object(XWiki.OIDC.UserClass) as oidc where oidc.issuer = :issuer')
  .bindValue('issuer', ISSUER).execute().each { linkedSubjects[it[0]] = it[1] }
def profiles = services.query.xwql('select doc.fullName, ldap.uid from Document doc, doc.object(XWiki.LDAPProfileClass) as ldap order by doc.fullName').execute()
println "|=Profile|=LDAP uid|=Subject|=Result"
profiles.each { row ->
  def (fullName, uid) = [row[0], row[1]]
  def upn = uid ? upnByUid[uid.toLowerCase()] : null
  def doc = wiki.getDocument(services.model.resolveDocument(fullName), context)
  def result
  if (doc.getXObject(oidcClass) != null) {
    result = 'skipped: already linked'
  } else if (upn == null) {
    result = '(% style="color:red" %)skipped: no UPN in CSV'
  } else if (linkedSubjects.containsKey(upn)) {
    result = "(% style=\"color:red\" %)conflict: subject already on ${linkedSubjects[upn]}"
  } else if (DRY_RUN) {
    result = 'would link'
  } else {
    doc = doc.clone()
    def obj = doc.newXObject(oidcClass, context)
    obj.setStringValue('issuer', ISSUER)
    obj.setStringValue('subject', upn)
    wiki.saveDocument(doc, 'Link LDAP profile to OIDC identity', true, context)
    result = 'linked'
  }
  println "|[[${fullName}]]|${uid}|${upn ?: ''}|${result}"
}
{{/groovy}}{code}
 
cid:jira-generated-image-avatar-39f467d7-e410-4f81-823d-604bae70da61 Patrick Koska on 04/Nov/26 11:23
 
Migration is usually a one-time operation. This probably is not worth a dedicated feature since setups and prior authentication methods might vary significantly. IMHO a script is the most flexible and easiest way to go.

The current implementation of OpenID Connect Authenticator only adds XWiki.OIDC.UserClass when creating new users. As far as I can tell it always uses the profile where the correspoding XWiki.OIDC.UserClass exists from then on. So basically you need to add an XWiki.OIDC.UserClass object to your existing LDAP users (or whatever auth method you've been using so far).

I solved it by parsing for users with an XWiki.LDAPProfileClass object and adding an XWiki.OIDC.UserClass object to their profiles.
Below you'll find See below for the script I used.

Hint: Do steps 2–4 before users start logging in through OIDC, so no new duplicates appear in between
# Back up the database
# Fill in {{ISSUER}} and {{{}CSV{}}}, then view the page with {{{}DRY_RUN = true{}}}.
# Fix any red rows: missing UPNs, or conflicts with profiles from earlier test logins
# Set {{DRY_RUN = false}} and view the page once more.
# Set the OIDC configuration according to the format of your usernames, then restart: oidc.user.subjectFormater=${oidc.idtoken.upn._lowerCase}
# Delete duplicate profiles after checking they hold nothing
# If you use the Microsoft Entra v2.0 endpoint, make sure {{upn}} is in the ID token: add it as an optional claim under token configuration in the app registration

{code:java}
{{groovy}}
import org.xwiki.model.reference.LocalDocumentReference
def DRY_RUN = true
// Exactly the "iss" claim of the ID token (copy it from an OIDC-created profile)
def ISSUER = 'https://login.microsoftonline.com/<tenant-id>/v2.0'
// Contents of upn.csv: <LDAP uid>;<userPrincipalName>
def CSV = '''
user;user@example.com
'''
def upnByUid = [:]
CSV.trim().eachLine { line ->
  def parts = line.split(';')
  if (parts.length == 2) {
    upnByUid[parts[0].trim().toLowerCase()] = parts[1].trim().toLowerCase()
  }
}
def oidcClass = new LocalDocumentReference(['XWiki', 'OIDC'], 'UserClass')
def context = xcontext.context
def wiki = context.wiki
def linkedSubjects = [:]
services.query.xwql('select oidc.subject, doc.fullName from Document doc, doc.object(XWiki.OIDC.UserClass) as oidc where oidc.issuer = :issuer')
  .bindValue('issuer', ISSUER).execute().each { linkedSubjects[it[0]] = it[1] }
def profiles = services.query.xwql('select doc.fullName, ldap.uid from Document doc, doc.object(XWiki.LDAPProfileClass) as ldap order by doc.fullName').execute()
println "|=Profile|=LDAP uid|=Subject|=Result"
profiles.each { row ->
  def (fullName, uid) = [row[0], row[1]]
  def upn = uid ? upnByUid[uid.toLowerCase()] : null
  def doc = wiki.getDocument(services.model.resolveDocument(fullName), context)
  def result
  if (doc.getXObject(oidcClass) != null) {
    result = 'skipped: already linked'
  } else if (upn == null) {
    result = '(% style="color:red" %)skipped: no UPN in CSV'
  } else if (linkedSubjects.containsKey(upn)) {
    result = "(% style=\"color:red\" %)conflict: subject already on ${linkedSubjects[upn]}"
  } else if (DRY_RUN) {
    result = 'would link'
  } else {
    doc = doc.clone()
    def obj = doc.newXObject(oidcClass, context)
    obj.setStringValue('issuer', ISSUER)
    obj.setStringValue('subject', upn)
    wiki.saveDocument(doc, 'Link LDAP profile to OIDC identity', true, context)
    result = 'linked'
  }
  println "|[[${fullName}]]|${uid}|${upn ?: ''}|${result}"
}
{{/groovy}}{code}