import * as url from 'url' ; import { request } from './../../config' ; import * as cookie from 'cookie' ; import template = require ( 'lodash.template' ) ; const xmldoc = require ( 'xmldoc' ) ; import { IUserCredentials } from './../IAuthOptions' ; import { IAuthResponse } from './../IAuthResponse' ; import { Cache } from './../../utils/Cache' ; import * as consts from './../../Consts' ; import { AdfsHelper } from './../../utils/AdfsHelper' ; import { template as onlineSamlWsfedAdfsTemplate } from './../../templates/OnlineSamlWsfedAdfs' ; import { template as onlineSamlWsfedTemplate } from './../../templates/OnlineSamlWsfed' ; import { HostingEnvironment } from '../HostingEnvironment' ; import { OnlineResolver } from './base/OnlineResolver' ; import { Response } from 'got' ; export class OnlineUserCredentials extends OnlineResolver { private static CookieCache : Cache = new Cache ( ) ; constructor ( _siteUrl , private _authOptions ) { super ( _siteUrl ) ; this . _authOptions = Object . assign ( { } , _authOptions ) ; this . _authOptions . username = this . _authOptions . username . replace ( /&/g , '&amp;' ) . replace ( /"/g , '&quot;' ) . replace ( /'/g , '&apos;' ) . replace ( /</g , '&lt;' ) . replace ( />/g , '&gt;' ) ; this . _authOptions . password = this . _authOptions . password . replace ( /&/g , '&amp;' ) . replace ( /"/g , '&quot;' ) . replace ( /'/g , '&apos;' ) . replace ( /</g , '&lt;' ) . replace ( />/g , '&gt;' ) ; } public getAuth ( ) { const parsedUrl = url . parse ( this . _siteUrl ) ; const host = parsedUrl . host ; const cacheKey = `${ host }@${ this . _authOptions . username }@${ this . _authOptions . password }` ; const cachedCookie = OnlineUserCredentials . CookieCache . get < string > ( cacheKey ) ; if ( cachedCookie ) { return Promise . resolve ( { headers : { 'Cookie' : cachedCookie } } ) ; } return this . getSecurityToken ( ) . then ( xmlResponse => { return this . postToken ( xmlResponse ) ; } ) . then ( data => { const response = data [ 1 ] ; const diffSeconds = data [ 0 ] ; let fedAuth ; let rtFa ; for ( let i = 0 ; i < response . headers [ 'set-cookie' ] . length ; i ++ ) { const headerCookie = response . headers [ 'set-cookie' ] [ i ] ; if ( headerCookie . indexOf ( consts . FedAuth ) !== - 1 ) { fedAuth = cookie . parse ( headerCookie ) [ consts . FedAuth ] ; } if ( headerCookie . indexOf ( consts . RtFa ) !== - 1 ) { rtFa = cookie . parse ( headerCookie ) [ consts . RtFa ] ; } } const authCookie = 'FedAuth=' + fedAuth + '; rtFa=' + rtFa ; OnlineUserCredentials . CookieCache . set ( cacheKey , authCookie , diffSeconds ) ; return { headers : { 'Cookie' : authCookie } } ; } ) ; } protected InitEndpointsMappings ( ) { this . endpointsMappings . set ( HostingEnvironment . Production , 'login.microsoftonline.com' ) ; this . endpointsMappings . set ( HostingEnvironment . China , 'login.chinacloudapi.cn' ) ; this . endpointsMappings . set ( HostingEnvironment . German , 'login.microsoftonline.de' ) ; this . endpointsMappings . set ( HostingEnvironment . USDefence , 'login-us.microsoftonline.com' ) ; this . endpointsMappings . set ( HostingEnvironment . USGovernment , 'login-us.microsoftonline.com' ) ; } private getSecurityToken ( ) { return request . post ( this . OnlineUserRealmEndpoint , { form : { 'login' : this . _authOptions . username } } ) . json ( ) . then ( ( userRealm ) => { const authType = userRealm . NameSpaceType ; if ( ! authType ) { throw new Error ( 'Unable to define namespace type for Online authentiation' ) ; } if ( authType === 'Managed' ) { return this . getSecurityTokenWithOnline ( ) ; } if ( authType === 'Federated' ) { return this . getSecurityTokenWithAdfs ( userRealm . AuthURL , userRealm . CloudInstanceIssuerUri ) ; } throw new Error ( `Unable to resolve namespace authentiation type. Type received: ${ authType }` ) ; } ) ; } private getSecurityTokenWithAdfs ( adfsUrl , relyingParty ) { return AdfsHelper . getSamlAssertion ( { username : this . _authOptions . username , password : this . _authOptions . password , adfsUrl : adfsUrl , relyingParty : relyingParty || consts . AdfsOnlineRealm } ) . then ( samlAssertion => { const siteUrlParsed = url . parse ( this . _siteUrl ) ; const rootSiteUrl = siteUrlParsed . protocol + '//' + siteUrlParsed . host ; const tokenRequest = template ( onlineSamlWsfedAdfsTemplate ) ( { endpoint : rootSiteUrl , token : samlAssertion . value } ) ; return request . post ( this . MSOnlineSts , { body : tokenRequest , headers : { 'Content-Length' : tokenRequest . length . toString ( ) , 'Content-Type' : 'application/soap+xml; charset=utf-8' } , resolveBodyOnly : true } ) ; } ) ; } private getSecurityTokenWithOnline ( ) { const parsedUrl = url . parse ( this . _siteUrl ) ; const host = parsedUrl . host ; const spFormsEndPoint = `${ parsedUrl . protocol }//${ host }/${ consts . FormsPath }` ; const samlBody = template ( onlineSamlWsfedTemplate ) ( { username : this . _authOptions . username , password : this . _authOptions . password , endpoint : spFormsEndPoint } ) ; return request . post ( this . MSOnlineSts , { body : samlBody , resolveBodyOnly : true , headers : { 'Content-Type' : 'application/soap+xml; charset=utf-8' } } ) ; } private postToken ( xmlResponse ) { const xmlDoc = new xmldoc . XmlDocument ( xmlResponse ) ; const parsedUrl = url . parse ( this . _siteUrl ) ; const spFormsEndPoint = `${ parsedUrl . protocol }//${ parsedUrl . host }/${ consts . FormsPath }` ; const securityTokenResponse = xmlDoc . childNamed ( 'S:Body' ) . firstChild ; if ( securityTokenResponse . name . indexOf ( 'Fault' ) !== - 1 ) { throw new Error ( securityTokenResponse . toString ( ) ) ; } const binaryToken = securityTokenResponse . childNamed ( 'wst:RequestedSecurityToken' ) . firstChild . val ; const now = new Date ( ) . getTime ( ) ; const expires = new Date ( securityTokenResponse . childNamed ( 'wst:Lifetime' ) . childNamed ( 'wsu:Expires' ) . val ) . getTime ( ) ; const diff = ( expires - now ) / 1000 ; const diffSeconds = parseInt ( diff . toString ( ) , 10 ) ; return Promise . all ( [ Promise . resolve ( diffSeconds ) , request . post ( spFormsEndPoint , { headers : { 'User-Agent' : 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)' , 'Content-Type' : 'application/x-www-form-urlencoded' } , body : binaryToken } ) ] ) ; } private get MSOnlineSts ( ) : string { return `https://${ this . endpointsMappings . get ( this . hostingEnvironment ) }/extSTS.srf` ; } private get OnlineUserRealmEndpoint ( ) : string { return `https://${ this . endpointsMappings . get ( this . hostingEnvironment ) }/GetUserRealm.srf` ; } }