import assert from "assert" ; import os from "os" ; import { compare } from "semver" ; import { IRequest } from "@fluidframework/core-interfaces" ; import { IDocumentServiceFactory , IUrlResolver , } from "@fluidframework/driver-definitions" ; import type { OdspResourceTokenFetchOptions , HostStoragePolicy } from "@fluidframework/odsp-driver-definitions" ; import { OdspTokenConfig , OdspTokenManager , odspTokensCache , getMicrosoftConfiguration , } from "@fluidframework/tool-utils" ; import { getDriveId , getDriveItemByRootFileName , IClientConfig , } from "@fluidframework/odsp-doclib-utils" ; import { ITestDriver } from "@fluidframework/test-driver-definitions" ; import { OdspDriverApiType , OdspDriverApi } from "./odspDriverApi" ; const passwordTokenConfig = ( username , password ) : OdspTokenConfig => ( { type : "password" , username , password } ) ; interface IOdspTestLoginInfo { siteUrl : string ; username : string ; password : string ; supportsBrowserAuth ? : boolean ; } type TokenConfig = IOdspTestLoginInfo & IClientConfig ; interface IOdspTestDriverConfig extends TokenConfig { directory : string ; driveId : string ; options : HostStoragePolicy | undefined ; } interface LoginTenantRange { prefix : string ; start : number ; count : number ; password : string ; } interface LoginTenants { [ tenant ] : { range : LoginTenantRange ; } ; } param tenantIndex param requestedUserName function getCredentials ( tenantIndex , requestedUserName ? ) { const creds = { } ; const loginTenants = process . env . login__odsp__test__tenants ; if ( loginTenants !== undefined ) { const tenants = JSON . parse ( loginTenants ) ; const tenantNames = Object . keys ( tenants ) ; const tenant = tenantNames [ tenantIndex % tenantNames . length ] ; const tenantInfo = tenants [ tenant ] ; const range = tenantInfo . range ; for ( let i = 0 ; i < range . count ; i ++ ) { const username = `${ range . prefix }${ range . start + i }@${ tenant }` ; if ( requestedUserName === undefined || requestedUserName === username ) { creds [ username ] = range . password ; } } } else { const loginAccounts = process . env . login__odsp__test__accounts ; assert ( loginAccounts !== undefined , "Missing login__odsp__test__accounts" ) ; const passwords = JSON . parse ( loginAccounts ) ; const username = requestedUserName ?? Object . keys ( passwords ) [ 0 ] ; assert ( passwords [ username ] , `No password for username: ${ username }` ) ; creds [ username ] = passwords [ username ] ; } return creds ; } export class OdspTestDriver implements ITestDriver { private static readonly odspTokenManager = new OdspTokenManager ( odspTokensCache ) ; private static readonly driveIdPCache = new Map < string , Promise < string > > ( ) ; private static readonly legacyDriverUserRandomIndex = Math . random ( ) ; private static async getDriveIdFromConfig ( tokenConfig ) { const siteUrl = tokenConfig . siteUrl ; try { return await getDriveId ( siteUrl , "" , undefined , { accessToken : await this . getStorageToken ( { siteUrl , refresh : false } , tokenConfig ) , refreshTokenFn : async ( ) => this . getStorageToken ( { siteUrl , refresh : true } , tokenConfig ) } ) ; } catch ( ex ) { if ( tokenConfig . supportsBrowserAuth !== true ) { throw ex ; } } return getDriveId ( siteUrl , "" , undefined , { accessToken : await this . getStorageToken ( { siteUrl , refresh : false , useBrowserAuth : true } , tokenConfig ) , refreshTokenFn : async ( ) => this . getStorageToken ( { siteUrl , refresh : true , useBrowserAuth : true } , tokenConfig ) } ) ; } public static async createFromEnv ( config ? , api = OdspDriverApi ) { const tenantIndex = config ?. tenantIndex ?? 0 ; const creds = getCredentials ( tenantIndex , config ?. username ) ; const users = Object . keys ( creds ) ; const randomUserIndex = compare ( api . version , "0.46.0" ) >= 0 ? Math . random ( ) : OdspTestDriver . legacyDriverUserRandomIndex ; const userIndex = Math . floor ( randomUserIndex * users . length ) ; const username = users [ userIndex ] ; const emailServer = username . substr ( username . indexOf ( "@" ) + 1 ) ; let siteUrl ; let tenantName ; if ( emailServer . startsWith ( "http://" ) || emailServer . startsWith ( "https://" ) ) { tenantName = new URL ( emailServer ) . hostname ; siteUrl = emailServer ; } else { tenantName = emailServer . substr ( 0 , emailServer . indexOf ( "." ) ) ; siteUrl = `https://${ tenantName }.sharepoint.com` ; } const options = config ?. options ?? { } ; options . isolateSocketCache = true ; return this . create ( { username , password : creds [ username ] , siteUrl , supportsBrowserAuth : config ?. supportsBrowserAuth } , config ?. directory ?? "" , api , options , tenantName , userIndex ) ; } private static async getDriveId ( siteUrl , tokenConfig ) { let driveIdP = this . driveIdPCache . get ( siteUrl ) ; if ( driveIdP ) { return driveIdP ; } driveIdP = this . getDriveIdFromConfig ( tokenConfig ) ; this . driveIdPCache . set ( siteUrl , driveIdP ) ; try { return await driveIdP ; } catch ( e ) { this . driveIdPCache . delete ( siteUrl ) ; throw e ; } } private static async create ( loginConfig , directory , api = OdspDriverApi , options ? , tenantName ? , userIndex ? ) { const tokenConfig = { ... loginConfig , ... getMicrosoftConfiguration ( ) } ; const driveId = await this . getDriveId ( loginConfig . siteUrl , tokenConfig ) ; const directoryParts = [ directory ] ; if ( process . env . BUILD_BUILD_ID !== undefined ) { directoryParts . push ( process . env . BUILD_BUILD_ID ) ; } else { directoryParts . push ( os . hostname ( ) ) ; } const driverConfig = { ... tokenConfig , directory : directoryParts . join ( "/" ) , driveId , options } ; return new OdspTestDriver ( driverConfig , api , tenantName , userIndex ) ; } private static async getStorageToken ( options , config ) { const host = new URL ( options . siteUrl ) . host ; if ( options . useBrowserAuth === true ) { const browserTokens = await this . odspTokenManager . getOdspTokens ( host , config , { type : "browserLogin" , navigator : ( openUrl ) => { console . log ( `Open the following url in a new private browser window, and login with user: ${ config . username }` ) ; console . log ( `Additional account details may be available in the environment variable login__odsp__test__accounts` ) ; console . log ( `"${ openUrl }"` ) ; } } , options . refresh ) ; return browserTokens . accessToken ; } const tokens = await this . odspTokenManager . getOdspTokens ( host , config , passwordTokenConfig ( config . username , config . password ) , options . refresh ) ; return tokens . accessToken ; } public readonly type = "odsp" ; public get version ( ) { return this . api . version ; } private readonly testIdToUrl = new Map < string , string > ( ) ; private constructor ( private readonly config , private readonly api = OdspDriverApi , public readonly tenantName ? , public readonly userIndex ? ) { } param testId async createContainerUrl ( testId ) { if ( ! this . testIdToUrl . has ( testId ) ) { const siteUrl = this . config . siteUrl ; const driveItem = await getDriveItemByRootFileName ( this . config . siteUrl , undefined , `/${ this . config . directory }/${ testId }.fluid` , { accessToken : await this . getStorageToken ( { siteUrl , refresh : false } ) , refreshTokenFn : async ( ) => this . getStorageToken ( { siteUrl , refresh : false } ) } , false , this . config . driveId ) ; this . testIdToUrl . set ( testId , this . api . createOdspUrl ( { ... driveItem , siteUrl , dataStorePath : "/" } ) ) ; } return this . testIdToUrl . get ( testId ) ! ; } createDocumentServiceFactory ( ) { return new this . api . OdspDocumentServiceFactory ( this . getStorageToken . bind ( this ) , this . getPushToken . bind ( this ) , undefined , this . config . options ) ; } createUrlResolver ( ) { return new this . api . OdspDriverUrlResolver ( ) ; } createCreateNewRequest ( testId ) { return this . api . createOdspCreateContainerRequest ( this . config . siteUrl , this . config . driveId , this . config . directory , `${ testId }.fluid` ) ; } private async getStorageToken ( options ) { return OdspTestDriver . getStorageToken ( options , this . config ) ; } private async getPushToken ( options ) { const tokens = await OdspTestDriver . odspTokenManager . getPushTokens ( new URL ( options . siteUrl ) . host , this . config , passwordTokenConfig ( this . config . username , this . config . password ) , options . refresh ) ; return tokens . accessToken ; } public getUrlFromItemId ( itemId ) { return this . api . createOdspUrl ( { siteUrl : this . config . siteUrl , driveId : this . config . driveId , itemId , dataStorePath : "/" } ) ; } }