Gistは埋もれていく & Gistペタペタ系記事はQiitaのが楽なんじゃないか?ということで、久しぶりに Qiita に。
前提
- Provisioning は SCIM で。
- JIT Provisioning はしない。
- Email による紐付けではなく、ちゃんと Federation 用に識別子を発行して紐付けを行う。
- 再利用の可能性のある識別子を紐付けに使うべきではない。
SalesForce 向けの SCIM Provisioning サンプルコード。
SCIM の External ID に指定した識別子は SalesForce 側の Federation Identifier に入る。
require 'rack/oauth2'
Rack::OAuth2.debug!
client = Rack::OAuth2::Client.new(
identifier: '<YOUR-CLIENT-ID>',
secret: '<YOUR-CLIENT-SECRET>',
authorization_endpoint: 'https://login.salesforce.com/services/oauth2/authorize',
token_endpoint: 'https://login.salesforce.com/services/oauth2/token',
redirect_uri: '<YOUR-CALLBACK-URL>'
)
def endpoint_for(resource)
scim_base_endpoint = 'https://<YOUR-DOMAIN>.my.salesforce.com/services/scim/v1'
File.join(scim_base_endpoint, resource)
end
module JSONized
def request_to(resource, method: :get, params: nil)
response = send method, endpoint_for(resource), params.try(:to_json), 'Content-Type': 'application/json'
puts JSON.pretty_generate JSON.parse(response.body)
end
end
authorization_uri = client.authorization_uri(
scope: [:api]
)
`open "#{authorization_uri}"`
print 'code: ' and STDOUT.flush
code = gets.chop
client.authorization_code = code
token = client.access_token! :body
token.extend JSONized
# token.request_to 'Entitlements' # => ここのレスポンスから適切な Entitlement (e.g. Standard User) の識別子を取得しておく。
token.request_to 'Users', method: :post, params: {
externalId: '<OIDC-SUBJECT-VALUE>',
userName: 'some-user@your-idp.example.comp',
name: {
familyName: 'User',
givenName: 'Some'
},
emails: [{
value: 'some-user@your-idp.example.com'
}],
entitlements: [{
value: '<ENTITLEMETN-ID>'
}]
}
SalesForce の OIDC IdP 向け Registration Handler Apex Script。
Federation Identifier と OIDC の "sub" が一致すれば、その "sub" と当該 SalesForce アカウントを紐付ける。(createUser)
紐付け確定後は特に何もしない。 (updateUser)
public class OIDCRegHandler implements Auth.RegistrationHandler{
public User createUser(Id portalId, Auth.UserData data){
List<User> users = [SELECT Id FROM User WHERE FederationIdentifier =:data.identifier];
if (users.size() == 1) {
return users[0];
} else {
return null;
}
}
public void updateUser(Id userId, Id portalId, Auth.UserData data) {
}
}
補足
このサンプルでは、OIDC の Issuer はユーザー識別に用いていない。
OIDC は 原理的には Issuer & Subject のペアによりユーザーを一意に識別できることを保証しているのみで、Subject のみでユーザーを一意に識別することは保証しないプロトコルである。(SAML も同様である)
このサンプルは「あなたが当該 SalesForce テナントに設定する OpenID Connect IdP が一つのみであるため、Issuer はチェックせずとも固定値であることが確約されている」ということを前提としている点に注意すること。