-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Acquiring list of validDomains from CDN (#2089)
* Adding in validDomains json object and publishing it as an Artifact * Change files * Renamed utils folder to artifactsForCDN and explicitly referencing validDomains.json file in build-test-publish * Update @microsoft-teams-js-9f9754ac-4921-4f0b-a615-4498501e1ee1.json * Renamed validOrigins to validDomains * Updated reference from utils to artifactsForCDN * Testing validDomains acquisition from CDN * Moved validDomains.json into src folder and modified utils.ts to include reference to CDN * Removed async calls * Reverting back to main utils * Added fetch mock for jest tests * Change files * Removed console.log statements * Removing hardcoded list of validDomains * Moved validOriginsJson to constants * Added missing description for validOriginsFallback * Added in validDomains.ts file, changed the way we call functions, looking into PrivateApi.spec for why unsupported domains are failing/timing out * Updated tests to account for async processing of messages * Updated tests and added new unit tests for fallback logic * Added in 'caching' for validDomains * Fixed improper import of json file * Made JSON nomenclature more known * Added logging, renamed validDomains to validOrigins for consistency, added JSON validation, repalced badactor with badactor.example in unit tests * Added in SSR check * Changed validateOriginsFromCDN to isValidOriginsJSONVald * Fixed naming and try catch block * Updated SDF endpoint to PROD endpoint --------- Co-authored-by: Trevor Harris <[email protected]>
- Loading branch information
1 parent
41dd10e
commit edef8cc
Showing
42 changed files
with
1,305 additions
and
832 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@microsoft-teams-js-71f0dd2d-88b0-4651-9a93-2a52e32a466e.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "minor", | ||
"comment": "Added new feature to acquire list of valid origins from a CDN endpoint", | ||
"packageName": "@microsoft/teams-js", | ||
"email": "[email protected]", | ||
"dependentChangeType": "patch" | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import { validOriginsCdnEndpoint, validOriginsFallback } from './constants'; | ||
import { GlobalVars } from './globalVars'; | ||
import { getLogger } from './telemetry'; | ||
import { inServerSideRenderingEnvironment, isValidHttpsURL } from './utils'; | ||
|
||
let validOriginsCache: string[] = []; | ||
const validateOriginLogger = getLogger('validateOrigin'); | ||
|
||
export async function prefetchOriginsFromCDN(): Promise<void> { | ||
await getValidOriginsListFromCDN(); | ||
} | ||
|
||
function isValidOriginsCacheEmpty(): boolean { | ||
return validOriginsCache.length !== 0; | ||
} | ||
|
||
async function getValidOriginsListFromCDN(): Promise<string[]> { | ||
if (isValidOriginsCacheEmpty()) { | ||
return validOriginsCache; | ||
} | ||
if (!inServerSideRenderingEnvironment()) { | ||
return fetch(validOriginsCdnEndpoint) | ||
.then((response) => { | ||
if (!response.ok) { | ||
throw new Error('Invalid Response from Fetch Call'); | ||
} | ||
return response.json().then((validOriginsCDN) => { | ||
if (isValidOriginsJSONValid(JSON.stringify(validOriginsCDN))) { | ||
validOriginsCache = validOriginsCDN.validOrigins; | ||
return validOriginsCache; | ||
} else { | ||
throw new Error('Valid Origins List Is Invalid'); | ||
} | ||
}); | ||
}) | ||
.catch((e) => { | ||
validateOriginLogger('validOrigins fetch call to CDN failed with error: %s. Defaulting to fallback list', e); | ||
validOriginsCache = validOriginsFallback; | ||
return validOriginsCache; | ||
}); | ||
} else { | ||
validOriginsCache = validOriginsFallback; | ||
return validOriginsFallback; | ||
} | ||
} | ||
|
||
function isValidOriginsJSONValid(validOriginsJSON: string): boolean { | ||
let validOriginsCDN = JSON.parse(validOriginsJSON); | ||
try { | ||
validOriginsCDN = JSON.parse(validOriginsJSON); | ||
} catch (_) { | ||
return false; | ||
} | ||
if (!validOriginsCDN.validOrigins) { | ||
return false; | ||
} | ||
for (const validOrigin of validOriginsCDN.validOrigins) { | ||
try { | ||
new URL('https://' + validOrigin); | ||
} catch (_) { | ||
validateOriginLogger('isValidOriginsFromCDN call failed to validate origin: %s', validOrigin); | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* @param pattern - reference pattern | ||
* @param host - candidate string | ||
* @returns returns true if host matches pre-know valid pattern | ||
* | ||
* @example | ||
* validateHostAgainstPattern('*.teams.microsoft.com', 'subdomain.teams.microsoft.com') returns true | ||
* validateHostAgainstPattern('teams.microsoft.com', 'team.microsoft.com') returns false | ||
* | ||
* @internal | ||
* Limited to Microsoft-internal use | ||
*/ | ||
function validateHostAgainstPattern(pattern: string, host: string): boolean { | ||
if (pattern.substring(0, 2) === '*.') { | ||
const suffix = pattern.substring(1); | ||
if ( | ||
host.length > suffix.length && | ||
host.split('.').length === suffix.split('.').length && | ||
host.substring(host.length - suffix.length) === suffix | ||
) { | ||
return true; | ||
} | ||
} else if (pattern === host) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* @internal | ||
* Limited to Microsoft-internal use | ||
*/ | ||
export function validateOrigin(messageOrigin: URL): Promise<boolean> { | ||
return getValidOriginsListFromCDN().then((validOriginsList) => { | ||
// Check whether the url is in the pre-known allowlist or supplied by user | ||
if (!isValidHttpsURL(messageOrigin)) { | ||
validateOriginLogger( | ||
'Origin %s is invalid because it is not using https protocol. Protocol being used: %s', | ||
messageOrigin, | ||
messageOrigin.protocol, | ||
); | ||
return false; | ||
} | ||
const messageOriginHost = messageOrigin.host; | ||
if (validOriginsList.some((pattern) => validateHostAgainstPattern(pattern, messageOriginHost))) { | ||
return true; | ||
} | ||
|
||
for (const domainOrPattern of GlobalVars.additionalValidOrigins) { | ||
const pattern = domainOrPattern.substring(0, 8) === 'https://' ? domainOrPattern.substring(8) : domainOrPattern; | ||
if (validateHostAgainstPattern(pattern, messageOriginHost)) { | ||
return true; | ||
} | ||
} | ||
|
||
validateOriginLogger( | ||
'Origin %s is invalid because it is not an origin approved by this library or included in the call to app.initialize.\nOrigins approved by this library: %o\nOrigins included in app.initialize: %o', | ||
messageOrigin, | ||
validOriginsList, | ||
GlobalVars.additionalValidOrigins, | ||
); | ||
return false; | ||
}); | ||
} |
Oops, something went wrong.