Skip to content

Commit

Permalink
Imports SAML IdP user info mapping rules
Browse files Browse the repository at this point in the history
RESOLVES OKTA-124130
8ac0n: test

OKTA-124130
<<<Jenkins Check-In of Tested SHA: 4235656 for [email protected]>>>
Artifact: stormpath-migration
  • Loading branch information
rchild-okta authored and jenkins-okta committed Apr 28, 2017
1 parent 903afc4 commit dc42b91
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 14 deletions.
87 changes: 75 additions & 12 deletions functions/add-and-map-idp-attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const rs = require('../util/request-scheduler');
const MAPPINGS_PATH = `/api/internal/v1/mappings`;
const APPS_PATH = '/api/v1/apps';
const TYPES_PATH = `${APPS_PATH}/user/types`;
const SCHEMAS_PATH = '/api/v1/meta/schemas/user/default';

function getExternalToAppUserMap(schema) {
const map = {};
Expand All @@ -39,21 +40,74 @@ async function getSchemaInfo(idpId) {

// Imported schema for the IdP - this gives us any additional attributes
// that have not already been mapped by default.
const imported = await rs.get(`${APPS_PATH}/${idpId}/user/imported/schema`);
schemas.imported = imported;

for (let schemaType of Object.keys(schemas)) {
externalToAppUserMap[schemaType] = getExternalToAppUserMap(schemas[schemaType]);
if (types.type === 'IMPORTED') {
schemas.imported = await rs.get(`${APPS_PATH}/${idpId}/user/imported/schema`);
for (let schemaType of Object.keys(schemas)) {
externalToAppUserMap[schemaType] = getExternalToAppUserMap(schemas[schemaType]);
}
} else {
schemas.imported = {};
externalToAppUserMap.custom = getExternalToAppUserMap(schemas.custom);
externalToAppUserMap.base = externalToAppUserMap.imported = {};
}

return { typeId: types.id, schemas, externalToAppUserMap };
return { typeId: types.id, type: types.type, schemas, externalToAppUserMap };
} catch (err) {
throw new ApiError(`Failed to get schema info for idpId=${idpId}`, err);
}
}

async function addIdpAttributes(idpId, schemaInfo, mappings) {
logger.verbose(`Adding attributes to idpId=${idpId} externalNames=[${mappings.map(map => map.from)}]`);
async function addSamlAttributes(idpId, schemaInfo, mappings) {
logger.verbose(`Adding attributes to saml idpId=${idpId} externalNames=[${mappings.map(m => m.externalName)}]`);
try {
const missing = [];
const existing = [];
const userSchema = await rs.get(SCHEMAS_PATH);
const userProperties = Object.keys(userSchema.definitions).reduce((memo, type) => {
return Object.assign(memo, userSchema.definitions[type].properties);
}, {});

for (let mapping of mappings) {
if (schemaInfo.externalToAppUserMap.custom[mapping.externalName]) {
existing.push(mapping);
}
else if (!userProperties[mapping.userAttribute]) {
logger.warn(`Mapping found for unsupported user attribute ${mapping.userAttribute}, skipping`);
}
else {
missing.push(mapping);
}
}

if (existing.length > 0) {
logger.exists(`IdP attributes: ${existing.map(m => m.externalName)}`);
}

if (missing.length === 0) {
return;
}

logger.verbose(`Adding missing attributes to idpId=${idpId}: ${missing.map(m => m.externalName)}`);
const customSchema = Object.assign({}, schemaInfo.schemas.custom);

for (let mapping of missing) {
const property = userProperties[mapping.userAttribute];
property.externalName = mapping.externalName;
customSchema.schema.properties[mapping.externalName] = property;
}

await rs.put({
url: `${TYPES_PATH}/${schemaInfo.typeId}/schemas/${schemaInfo.schemas.custom.id}`,
body: customSchema
});
logger.created(`IdP attributes: ${missing.map(m => m.externalName)}`);
} catch (err) {
throw new ApiError(`Failed to add attributes for idpId=${idpId}`, err);
}
}

async function addSocialAttributes(idpId, schemaInfo, mappings) {
logger.verbose(`Adding attributes to social idpId=${idpId} externalNames=[${mappings.map(m => m.externalName)}]`);

const missing = [];
const existing = [];
Expand Down Expand Up @@ -101,8 +155,12 @@ async function addIdpAttributes(idpId, schemaInfo, mappings) {
}
}

async function mapIdpAttributes(idpId, schemaInfo, mappings) {
async function mapIdpAttributes(idpId, mappings) {
logger.verbose(`Mapping attributes for idpId=${idpId}`);

// Reload schemaInfo because it can change after adding attributes
const schemaInfo = await getSchemaInfo(idpId);

const userTypes = await rs.get('/api/v1/user/types/default?expand=schema');
const res = await rs.get({
url: MAPPINGS_PATH,
Expand Down Expand Up @@ -191,13 +249,18 @@ async function mapIdpAttributes(idpId, schemaInfo, mappings) {
async function addAndMapIdpAttributes(idpId, mappings) {
logger.verbose(`Adding and mapping attributes for idpId=${idpId}`);
if (mappings.length === 0) {
logger.verbose('No mappings for idpId=${idpId}');
logger.verbose(`No mappings for idpId=${idpId}`);
return;
}

const schemaInfo = await getSchemaInfo(idpId);
await addIdpAttributes(idpId, schemaInfo, mappings);
return mapIdpAttributes(idpId, schemaInfo, mappings);
if (schemaInfo.type === 'IMPORTED') {
await addSocialAttributes(idpId, schemaInfo, mappings);
} else {
await addSamlAttributes(idpId, schemaInfo, mappings);
}

return mapIdpAttributes(idpId, mappings);
}

module.exports = addAndMapIdpAttributes;
1 change: 1 addition & 0 deletions functions/create-saml-idp.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ function getIdpJson(options, kid) {
maxClockSkew: 120000,
provisioning: {
action: 'AUTO',
profileMaster: true,
groups: {
action: 'NONE'
}
Expand Down
5 changes: 3 additions & 2 deletions migrators/migrate-directories.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async function migrateSocial(type, directory) {
return linkUsersFromSocialDirectory(directory.id);
}

function migrateSaml(directory) {
async function migrateSaml(directory) {
const provider = directory.provider;
let requestAlgorithm;
switch (provider.requestSignatureAlgorithm) {
Expand All @@ -78,12 +78,13 @@ function migrateSaml(directory) {
logger.error(`Invalid request algorithm: ${provider.requestSignatureAlgorithm}`);
return;
}
return createSamlIdp({
const idp = await createSamlIdp({
signingCert: directory.signingCert,
name: `dir:${directory.name}`,
requestSignatureAlgorithm: requestAlgorithm,
ssoLoginUrl: provider.ssoLoginUrl
});
return addAndMapIdpAttributes(idp.id, directory.attributeMappings);
}

async function migrateDirectory(directory) {
Expand Down

0 comments on commit dc42b91

Please sign in to comment.