├── .gitignore ├── 20220922085530.png ├── 20220929154543.png ├── 20220930153822.png ├── LICENSE ├── ca ├── conditions │ ├── Applications.js │ ├── Locations.js │ ├── Platforms.js │ ├── clientAppTypes.js │ ├── eval │ │ ├── Applications.js │ │ ├── Locations.js │ │ ├── Platforms.js │ │ ├── clientAppTypes.js │ │ └── users.js │ └── users.js ├── main.js ├── mainPlugins │ ├── axiosh.js │ ├── clearPolicyCache.js │ ├── customPolicyFilter.js │ ├── extraFiltering.js │ ├── findEmptySub.js │ ├── getPol.js │ ├── getPol2.js │ ├── graphHelpers.js │ ├── grouper.js │ ├── inMemList.js │ ├── tokenHandler.js │ ├── uniqNonterm.js │ ├── userMap.js │ └── userMappers │ │ ├── disabled_directoryRoleTemplate.js │ │ └── group.js └── permutationsPerPolicy.js ├── creatUids.js ├── createCSVreport.js ├── createReport.js ├── createReportLegacy.js ├── demodata ├── example1-2.json ├── extreme.json ├── extremelyNarrow.json ├── extremelyNarrow2.json ├── largePol.json ├── lotOfGroups.json ├── multipleGuestTypes.json ├── namedLocations.json ├── policies.json ├── policies2.json ├── testMapping.json ├── wLa.json └── withOnePerm.json ├── docs └── example.md ├── img ├── 20220913101911.png ├── 20220913102419.png ├── 20220913102436.png ├── 20220913110303.png ├── 20220913111008.png └── controlfolder.png ├── init.sh ├── logic.md ├── package-lock.json ├── package.json ├── plugins └── evallIst.js ├── readme.md ├── run.md └── tokenHandler ├── axioshelpers.js ├── getCode.js ├── readme.md └── refresh.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | md/conversions/** 3 | test.js 4 | test2.js 5 | removeP.js 6 | rollback.js 7 | #Exclude all log files 8 | *.LOG 9 | *.txt 10 | *.json 11 | *.JSON 12 | *.pdf 13 | *.csv 14 | *.log 15 | # exclude from JSON and MD flag 16 | !_launch.json 17 | !package.json 18 | !package-lock.json 19 | !/providers/** 20 | !/demodata/** 21 | !/docs/** 22 | *.md 23 | !east.md 24 | !gitCheat.md 25 | !demo.md 26 | !readme.md 27 | !logic.md 28 | !dev-guide.md 29 | !example.md 30 | !run.md 31 | # Exlcude all docx files 32 | *.docx 33 | !pandoc-template-horizontal.docx 34 | !pandoc-template.docx 35 | !prepend.docx 36 | 37 | 38 | -------------------------------------------------------------------------------- /20220922085530.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsa2/caOptics/51b5374484005b0164af85258d34ca472c4b267b/20220922085530.png -------------------------------------------------------------------------------- /20220929154543.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsa2/caOptics/51b5374484005b0164af85258d34ca472c4b267b/20220929154543.png -------------------------------------------------------------------------------- /20220930153822.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsa2/caOptics/51b5374484005b0164af85258d34ca472c4b267b/20220930153822.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Joosua Santasalo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ca/conditions/Applications.js: -------------------------------------------------------------------------------- 1 | 2 | // Handle all clientApp conditions 3 | 4 | const { iteratePolicyKeys } = require("../mainPlugins/getPol") 5 | 6 | 7 | module.exports=async function (policy,conditions,plugin) { 8 | 9 | 10 | if (conditions?.length == 0) { 11 | return [] 12 | } 13 | 14 | let results = [] 15 | 16 | 17 | let included = iteratePolicyKeys('includeApplications',policy,[])[0]?.includeApplications 18 | let excluded = iteratePolicyKeys('excludeApplications',policy,[])[0]?.excludeApplications 19 | 20 | // check evaluators for incomplete inclusions 21 | 22 | if (!included?.includes('All') && (included?.length > 0)) { 23 | // exclude also the included for later stage review 24 | 25 | included.forEach( s=> results.push(s)) 26 | results.push('All') 27 | //included?.forEach(s => results.push(s)) 28 | excluded?.forEach(s => results.push(s)) 29 | 30 | 31 | } else { 32 | excluded?.forEach(s => results.push(s)) 33 | } 34 | 35 | let final =[] 36 | new Set(results).forEach(item => final.push(`${plugin}:${item}`)) 37 | 38 | 39 | return final 40 | 41 | 42 | 43 | 44 | } -------------------------------------------------------------------------------- /ca/conditions/Locations.js: -------------------------------------------------------------------------------- 1 | 2 | // Handle all evaluator conditions 3 | 4 | const { getLocations, iteratePolicyKeys } = require("../mainPlugins/getPol") 5 | 6 | /* 7 | 1. Case one, just excluded location for trusted 8 | 2. 9 | */ 10 | module.exports=async function (policy,conditions,plugin) { 11 | 12 | //console.log('policy being checked',policy?.displayName) 13 | if (conditions?.length == 0) { 14 | return [] 15 | } 16 | 17 | let results = [] 18 | let locations = await getLocations() 19 | let included = iteratePolicyKeys('includeLocations',policy,[])[0]?.includeLocations?.filter(s => locations.find(d => d?.id == s && !d?.isTrusted)) 20 | let excluded = iteratePolicyKeys('excludeLocations',policy,[])[0]?.excludeLocations?.filter(s => locations.find(d => d?.id == s && !d?.isTrusted)) 21 | 22 | // check evaluators for incomplete inclusions 23 | 24 | if (!included?.includes('All') && (included?.length > 0)) { 25 | // exclude also the included for later stage review 26 | 27 | included.forEach( s=> results.push(s)) 28 | results.push('All') 29 | //included?.forEach(s => results.push(s)) 30 | excluded?.forEach(s => results.push(s)) 31 | 32 | 33 | } else { 34 | excluded?.forEach(s => results.push(s)) 35 | } 36 | 37 | return results.map(item => `${plugin}:${item}`) 38 | 39 | 40 | } -------------------------------------------------------------------------------- /ca/conditions/Platforms.js: -------------------------------------------------------------------------------- 1 | const { iteratePolicyKeys } = require("../mainPlugins/getPol") 2 | 3 | // Handle all clientApp conditions 4 | /* */ 5 | 6 | module.exports=async function (policy,conditions,plugin) { 7 | 8 | if (conditions.length == 0) { 9 | return [] 10 | } 11 | 12 | let def= [ 13 | "android", 14 | "iOS", 15 | "windows", 16 | "windowsPhone", 17 | "macOS", 18 | "linux", 19 | ] 20 | 21 | let included = iteratePolicyKeys('includePlatforms',policy,[])[0]?.includePlatforms 22 | 23 | if (included.includes('all')) { 24 | included = def 25 | } 26 | 27 | let excluded = iteratePolicyKeys('excludePlatforms',policy,[])[0]?.excludePlatforms 28 | let results = [] 29 | 30 | 31 | let iteration = def.filter( def => !included.includes(def)) 32 | excluded?.filter(e => !iteration.includes(e)).forEach(r => iteration.push(r)) 33 | /* included.forEach(is => iteration.push(is)) 34 | */ 35 | //console.log(iteration) 36 | 37 | 38 | 39 | 40 | return iteration.map(item => `${plugin}:${item}`) 41 | 42 | 43 | } -------------------------------------------------------------------------------- /ca/conditions/clientAppTypes.js: -------------------------------------------------------------------------------- 1 | 2 | // Handle all evaluator conditions 3 | 4 | const { argv } = require("yargs") 5 | 6 | 7 | module.exports=async function (policy,conditions,plugin) { 8 | 9 | let results = [] 10 | let evaluator = conditions[0][plugin] 11 | // check evaluators for incomplete inclusions 12 | 13 | if (!evaluator.includes('all')) { 14 | 15 | /* if (!evaluator.includes('browser')) { 16 | results.push('browser') 17 | } 18 | if (!evaluator.includes('mobileAppsAndDesktopClients')) { 19 | results.push('mobileAppsAndDesktopClients') 20 | } */ 21 | 22 | results.push('mobileAppsAndDesktopClients') 23 | results.push('browser') 24 | 25 | if (argv.includeLegacyAuth) { 26 | results.push('other') 27 | results.push('exchangeActiveSync') 28 | } 29 | 30 | 31 | 32 | } 33 | 34 | return results.map(item => `${plugin}:${item}`) 35 | 36 | 37 | } -------------------------------------------------------------------------------- /ca/conditions/eval/Applications.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | module.exports= function (lookup, innerPol,currentPolicy) { 5 | res = innerPol.filter(p => (p?.conditions.applications.includeApplications.includes(lookup[1]) 6 | || p?.conditions.applications.includeApplications.includes('All'))) 7 | .filter(p =>!p?.conditions.applications.excludeApplications.includes(lookup[1])) 8 | 9 | 10 | return res 11 | 12 | } 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ca/conditions/eval/Locations.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | module.exports= function (lookup, innerPol,currentPolicy) { 5 | 6 | res = innerPol.filter(p => ( p?.conditions.locations?.includeLocations.includes(lookup[1]) 7 | || p?.conditions?.locations?.includeLocations.includes('All') || !p?.conditions?.locations) 8 | && !p?.conditions?.locations?.excludeLocations.includes(lookup[1]) ) 9 | 10 | 11 | 12 | 13 | 14 | return res 15 | 16 | 17 | } 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ca/conditions/eval/Platforms.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | module.exports= function (lookup, innerPol,currentPolicy) { 5 | 6 | 7 | if (currentPolicy) { 8 | 9 | 10 | } 11 | // Count stage 1 policies 12 | let stage1 = innerPol.filter(p => ( p?.conditions?.platforms == null)) 13 | 14 | res = innerPol.filter(p => p?.conditions !== null) 15 | .filter(p => p?.conditions?.platforms?.includePlatforms?.includes('all') || p?.conditions?.platforms?.includePlatforms.includes(lookup[1]) ) 16 | .filter(p => !p?.conditions?.platforms?.excludePlatforms?.includes(lookup[1]) ) 17 | 18 | 19 | // if there were events in stage1 flat 20 | stage1.forEach(s => res.push(s)) 21 | 22 | return res = res.flat() 23 | 24 | } 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ca/conditions/eval/clientAppTypes.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | module.exports= function (lookup, innerPol,currentPolicy) { 5 | 6 | res = innerPol.filter(p => p?.conditions.clientAppTypes?.includes(lookup[1]) || p?.conditions.clientAppTypes?.includes('all') ) 7 | // console.log(res) 8 | 9 | return res 10 | } 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ca/conditions/eval/users.js: -------------------------------------------------------------------------------- 1 | const { iteratePolicyKeys, iteratePolicyKeyNonArray } = require("../../mainPlugins/getPol") 2 | const { inMemoryList } = require("../../mainPlugins/inMemList") 3 | const {argv} = require("yargs"); 4 | 5 | 6 | 7 | module.exports= function (lookup, innerPol,currentPolicy) { 8 | 9 | const list = inMemoryList() 10 | let res = [] 11 | innerPol.forEach(policy => { 12 | 13 | 14 | try { 15 | let terminated = false 16 | 17 | let nestedInclude = iteratePolicyKeys('include',iteratePolicyKeyNonArray('users',policy,[]),[]) 18 | let nestedExclude = iteratePolicyKeys('exclude',iteratePolicyKeyNonArray('users',policy,[]),[]) 19 | // require('fs').writeFileSync('string.json',JSON.stringify(nestedInclude)) 20 | if (JSON.stringify(nestedInclude) == JSON.stringify([{"includeUsers":["All"]}])) { 21 | terminated = true 22 | } 23 | 24 | if (JSON.stringify(nestedInclude).match(lookup[1])) { 25 | terminated = true 26 | } 27 | 28 | //keep for debugging 29 | /* if (lookup[1] == "All" && policy.displayName.match('Baseline')) { 30 | console.log() 31 | } */ 32 | 33 | if (JSON.stringify(nestedExclude).match(lookup[1])) { 34 | terminated = false 35 | return res 36 | } 37 | 38 | 39 | // double checks via mapping if unterminated permutations 40 | if (argv.mapping && (nestedInclude?.length > 0 ||nestedExclude?.length > 0 )) { 41 | 42 | let inLink = ['includeUsers','includeGroups','includeRoles'] 43 | .map(type => iteratePolicyKeys(type,nestedInclude,[]).map( s=> s?.[type].map( ts => `${ts}:${lookup[1]}`) ) 44 | .flat() ) 45 | .flat() 46 | 47 | 48 | let unLink = ['excludeUsers','excludeGroups','excludeRoles'] 49 | .map(type => iteratePolicyKeys(type,nestedExclude,[]).map( s=> s?.[type].map( ts => `${ts}:${lookup[1]}`) ) 50 | .flat() ) 51 | .flat() 52 | 53 | 54 | 55 | 56 | //keep for debugging 57 | /* if (lookup[1] == "c66aad40-1dee-43c2-810b-77532250bfc8" && policy.displayName.match('francies')) { 58 | console.log() 59 | } */ 60 | 61 | if ( inLink.find( link => list.includes(link)) ) { 62 | terminated=true 63 | } 64 | 65 | if ( unLink.find( link => list.includes(link) ) ) { 66 | 67 | terminated=false 68 | } 69 | 70 | 71 | 72 | 73 | } 74 | 75 | if (terminated == true) { 76 | res.push(policy) 77 | } 78 | 79 | 80 | } catch(error) { 81 | console.log(error) 82 | } 83 | 84 | }) 85 | 86 | 87 | return res 88 | } 89 | -------------------------------------------------------------------------------- /ca/conditions/users.js: -------------------------------------------------------------------------------- 1 | 2 | // Handle all evaluator conditions 3 | 4 | const { argv } = require("yargs") 5 | 6 | const { iteratePolicyKeys } = require("../mainPlugins/getPol") 7 | 8 | 9 | module.exports=async function (policy,conditions,plugin) { 10 | 11 | results = [] 12 | 13 | const list = ['includeUsers','excludeUsers','includeGroups','excludeGroups','includeRoles','excludeRoles'].map(type => { 14 | let values = iteratePolicyKeys(type,policy,[]).map( s=> s?.[type]).flat() 15 | 16 | return values.filter(s => s?.length !== 0) 17 | 18 | }).flat() 19 | 20 | // Push "All" always to create proper lookup 21 | 22 | if (!list.includes('All') && !list.includes('GuestsOrExternalUsers')) { 23 | list.push('GuestsOrExternalUsers') 24 | } 25 | 26 | list.push('All') 27 | 28 | console.log(list) 29 | let clean = [] 30 | new Set(list).forEach( s=> clean.push(s) ) 31 | 32 | if (argv.skipObjectId) { 33 | let noList = argv.skipObjectId.split(',') 34 | clean = clean.filter( s => !noList.find( l => s == l)) 35 | } 36 | 37 | 38 | if (argv?.skip) { 39 | if (argv?.skip.split(',').includes('users')) { 40 | return; 41 | } 42 | } 43 | 44 | return clean.map(item => `${plugin}:${item}`) 45 | 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /ca/main.js: -------------------------------------------------------------------------------- 1 | const { getPolicies, iteratePolicyKeys, TerminatedPolicyConditionsLookupFull, nTerminatedPolicyConditionsLookupFull, getLocations } = require("./mainPlugins/getPol"); 2 | const fs = require('fs'); 3 | const { permutationList } = require("./permutationsPerPolicy"); 4 | const { rp } = require("../createReport"); 5 | const { getGraphTokenReducedScope } = require("./mainPlugins/tokenHandler"); 6 | const chalk = require("chalk"); 7 | const { userMap } = require("./mainPlugins/userMap"); 8 | const { argv } = require("yargs"); 9 | const { nonTerm } = require("./mainPlugins/uniqNonterm"); 10 | const { clearPolicyCache, clearTokenCache, clearMappingCache } = require("./mainPlugins/clearPolicyCache"); 11 | const { getPermutations } = require("./mainPlugins/getPol2"); 12 | const { rpCsv } = require("../createCSVreport"); 13 | const { rpLegacy } = require("../createReportLegacy"); 14 | const { decode } = require("jsonwebtoken"); 15 | const { inMemoryList } = require("./mainPlugins/inMemList"); 16 | const { extraFilter } = require("./mainPlugins/extraFiltering"); 17 | 18 | 19 | main() 20 | 21 | async function main() { 22 | 23 | if (argv.clearPolicyCache) { 24 | try { 25 | clearPolicyCache() 26 | } catch (error) { 27 | var msg = `no tokens to clear, or no policies to clear --> continuing: ${error.message}` 28 | console.log(chalk.yellow(msg)) 29 | } 30 | 31 | } 32 | 33 | if (argv.clearTokenCache) { 34 | try { 35 | clearTokenCache() 36 | } catch (error) { 37 | var msg = `no tokens to clear, or no policies to clear --> continuing: ${error.message}` 38 | console.log(chalk.yellow(msg)) 39 | } 40 | 41 | } 42 | 43 | if (argv.clearMappingCache) { 44 | clearMappingCache() 45 | } 46 | 47 | 48 | 49 | if (argv.clearTokenCache) { 50 | 51 | } 52 | 53 | // precheck for policies count 54 | try { 55 | await getGraphTokenReducedScope() 56 | await getLocations() 57 | const preP = await getPolicies() 58 | 59 | if (preP?.length == 0) { 60 | console.log(chalk.red('no policies, exiting')) 61 | return; 62 | } 63 | } catch (error) { 64 | console.log(chalk.red('unable to fetch policies, exiting', error?.message || 'no additional error message')) 65 | return; 66 | } 67 | 68 | 69 | if (argv.skipObjectId) { 70 | console.log(chalk.green(`Excluding certain objectID's due to provided arguments ${argv.skipObjectId}`)) 71 | } 72 | 73 | 74 | 75 | 76 | try { 77 | console.log(chalk.green('checking pre-requisites')) 78 | await getGraphTokenReducedScope() 79 | // check login and token validity 80 | } catch (error) { 81 | console.log('failing of getting token', error) 82 | return; 83 | } 84 | 85 | var policies = await getPolicies() 86 | 87 | // remove all policies which include only legacyAuth conditions 88 | if (!argv.includeLegacyAuth) { 89 | policies = policies.filter(p => p?.conditions?.clientAppTypes.includes('all') 90 | || p?.conditions?.clientAppTypes.includes('browser') 91 | || p?.conditions?.clientAppTypes.includes('mobileAppsAndDesktopClients')) 92 | 93 | console.log(policies) 94 | } 95 | 96 | /* 97 | 106 | */ 107 | 108 | if (argv.mapping) { 109 | await userMap(policies) 110 | } 111 | 112 | 113 | console.log('user mapping done') 114 | const evaluationPlugins = fs.readdirSync(`${__dirname}/conditions/`).filter(s => s?.match('js')) 115 | 116 | 117 | const fullPermutation = [] 118 | // For each policy handle the conditions 119 | // Before calculation of permutations 120 | 121 | for await (let policy of policies) { 122 | 123 | console.log('inspecting policy:', policy?.displayName) 124 | const perPolicyPermutations = [] 125 | for await (let plugin of evaluationPlugins) { 126 | 127 | try { 128 | let cases = iteratePolicyKeys(`${plugin.split('.js')[0]}`, policy, []) 129 | let results = await require(`./conditions/${plugin}`)(policy, cases, `${plugin.split('.js')[0]}`) 130 | // console.log(results) 131 | if (results?.length > 0) { 132 | 133 | 134 | perPolicyPermutations.push(results) 135 | 136 | results.forEach(re => fullPermutation.push(re)) 137 | } 138 | } catch (error) { 139 | console.log(plugin, 'plugin not available for', error) 140 | } 141 | 142 | } 143 | 144 | } 145 | 146 | [ 147 | 'users:GuestsOrExternalUsers', 148 | 'users:All', 149 | 'Applications:All', 150 | 'Platforms:All' 151 | ].forEach(u => fullPermutation.push(u)) 152 | 153 | 154 | let uniq = [] 155 | var expandedUniq = [] 156 | 157 | try { 158 | 159 | if (argv.mapping && argv.expand) { 160 | let expandedNonUniq = [] 161 | let inMemoryObjects = inMemoryList() 162 | 163 | argv.expand?.split(',').forEach(grp => { 164 | let flGroup = [] 165 | inMemoryObjects.filter(s => s.split(':')[0] == grp).forEach(filtered => { 166 | flGroup.push(`users:${filtered.split(':')[1]}`) 167 | }) 168 | 169 | flGroup.splice(0,argv.expandCount || 10).forEach( r => { 170 | expandedNonUniq.push(r) 171 | }) 172 | 173 | }) 174 | 175 | new Set(expandedNonUniq).forEach(s => { 176 | uniq.push(s) 177 | expandedUniq.push(s) 178 | }) 179 | console.log() 180 | 181 | } 182 | 183 | } catch (error) { 184 | 185 | console.log('invalid values for expander', error) 186 | } 187 | 188 | 189 | new Set(fullPermutation).forEach(s => uniq.push(s)) 190 | 191 | 192 | 193 | 194 | 195 | console.log('inspecting cross-policy mutations') 196 | // const mesh = permutationList(uniq) 197 | let mesh 198 | 199 | if (argv.aggressive) { 200 | // keep aggressive available for historical purposes 201 | mesh = permutationList(uniq) 202 | } else { 203 | // non aggressive permutation generation by default 204 | mesh = await getPermutations(uniq) 205 | //console.log(mesh) 206 | } 207 | 208 | 209 | 210 | 211 | const mockForMesh = { 212 | policy: { displayName: "All (cross-policy)" }, 213 | toProcessingPipeline: mesh 214 | } 215 | 216 | if (argv.dump) { 217 | fs.writeFileSync('mesh.json', JSON.stringify(mesh)) 218 | } 219 | 220 | console.log('unique items', uniq.length) 221 | 222 | let mix 223 | 224 | if (argv.mapping && argv.expand) { 225 | mix = await nTerminatedPolicyConditionsLookupFull([mockForMesh], policies,expandedUniq) 226 | } else { 227 | mix = await nTerminatedPolicyConditionsLookupFull([mockForMesh], policies) 228 | } 229 | 230 | 231 | 232 | 233 | 234 | if (argv.dump) { 235 | fs.writeFileSync('results.json', JSON.stringify(mix)) 236 | } 237 | 238 | console.log('unique same key order permutations across policies', mix?.length || 0) 239 | 240 | if (argv.aggressive) { 241 | 242 | let nq = nonTerm(mix) 243 | 244 | rpLegacy(nq, true, true) 245 | 246 | } else { 247 | 248 | /* 249 | // add userName and date to reports 250 | */ 251 | let now = new Date() 252 | let fileName = `report_day_${now.getDay()}_month_${now.getMonth()}_year_${now.getFullYear()}-tenant_${decode(await getGraphTokenReducedScope())?.tid}` 253 | 254 | console.log(chalk.green('writing report to', ` ${fileName}.csv`)) 255 | console.log(chalk.green('writing report to', ` ${fileName}.md`)) 256 | 257 | rp(mix, true, undefined, fileName, expandedUniq) 258 | 259 | rpCsv(mix, true, undefined, fileName,expandedUniq) 260 | 261 | } 262 | 263 | 264 | 265 | console.log(chalk.green('review md and csv reports for results')) 266 | 267 | return; 268 | 269 | 270 | 271 | } -------------------------------------------------------------------------------- /ca/mainPlugins/axiosh.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const qs = require('querystring') 3 | 4 | async function axiosClient2 (options, urlencoded, debug) { 5 | 6 | if (urlencoded == true) { 7 | options.data = qs.stringify(options.data) 8 | } 9 | if (debug) { 10 | //console.log(options) 11 | } 12 | 13 | var data = await axios(options).catch((error) => { 14 | /* console.log(error?.Error) */ 15 | return Promise.reject(error?.response?.data || error?.response?.status || error?.message) 16 | 17 | }) 18 | 19 | return data?.data || data.status 20 | 21 | } 22 | 23 | module.exports = {axiosClient2} -------------------------------------------------------------------------------- /ca/mainPlugins/clearPolicyCache.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | function clearMappingCache() { 5 | 6 | 7 | try { 8 | fs.unlinkSync(path.resolve(__dirname,'../../inMemoryResults.json')) 9 | } catch (error) { 10 | console.log('no policies to remove') 11 | } 12 | 13 | try { 14 | fs.unlinkSync(path.resolve(__dirname,'../../objectIds.json')) 15 | } catch (error) { 16 | console.log('no policies to remove') 17 | } 18 | 19 | try { 20 | fs.unlinkSync(path.resolve(__dirname,'../../appIds.json')) 21 | } catch (error) { 22 | console.log('no policies to remove') 23 | } 24 | 25 | 26 | } 27 | 28 | 29 | function clearPolicyCache() { 30 | 31 | try { 32 | fs.unlinkSync(path.resolve(__dirname,'../../policies.json')) 33 | } catch (error) { 34 | console.log('no policies to remove') 35 | } 36 | fs.unlinkSync(path.resolve(__dirname,'../../namedLocations.json')) 37 | try {} catch (error) { 38 | console.log('no locations to remove') 39 | } 40 | 41 | 42 | return; 43 | } 44 | 45 | function clearTokenCache() { 46 | 47 | try { 48 | fs.readdirSync('./tokenHandler/').filter(s => s.match(".json")).map(d => fs.unlinkSync(`./tokenHandler/${d}`)) 49 | } catch (error) { 50 | console.log('no tokens to remove') 51 | } 52 | 53 | return; 54 | } 55 | 56 | module.exports= {clearPolicyCache,clearTokenCache,clearMappingCache} -------------------------------------------------------------------------------- /ca/mainPlugins/customPolicyFilter.js: -------------------------------------------------------------------------------- 1 | module.exports=function (data) { 2 | 3 | // issue as per https://github.com/jsa2/caOptics/issues/4 4 | return data?.value.filter(s => 5 | s?.state == 'enabled' 6 | && s.conditions?.devices?.deviceFilter?.mode !== 'include' 7 | && s.conditions.applications?.includeApplications.length > 0 8 | && s.grantControls?.builtInControls 9 | // && s.conditions?.userRiskLevels == 0 10 | && s.conditions?.signInRiskLevels == 0 11 | ) 12 | 13 | 14 | } -------------------------------------------------------------------------------- /ca/mainPlugins/extraFiltering.js: -------------------------------------------------------------------------------- 1 | async function extraFilter(policies) { 2 | 3 | 4 | // Drop all conditions which don't have mfa, block or device based requirements 5 | policies = policies.filter(s => 6 | s?.grantControls?.builtInControls?.includes('mfa') || 7 | s?.grantControls?.builtInControls?.includes('block') || 8 | s?.grantControls?.builtInControls?.includes('domainJoinedDevice') || 9 | s?.grantControls?.builtInControls?.includes('compliantDevice') || 10 | s?.grantControls?.authenticationStrength 11 | ) 12 | // Policy cannot also be terminating when followed by OR and a weak condition 13 | policies = policies.filter(s => { 14 | if (s?.grantControls?.builtInControls.includes('approvedApplication') 15 | || s?.grantControls?.builtInControls.includes('compliantApplication') 16 | || s?.grantControls?.builtInControls?.termsOfUse) { 17 | if (s?.grantControls.operator !== "OR") { 18 | return s 19 | } 20 | 21 | } else { 22 | return s 23 | } 24 | }) 25 | 26 | return policies 27 | 28 | } 29 | 30 | module.exports = { extraFilter } -------------------------------------------------------------------------------- /ca/mainPlugins/findEmptySub.js: -------------------------------------------------------------------------------- 1 | function unPopulated(item, insertion) { 2 | 3 | if (Array.isArray(item)) { 4 | 5 | item.forEach(sub => { 6 | unPopulated(sub, insertion) 7 | }) 8 | 9 | } else { 10 | 11 | if (item?.subItems.length > 0) { 12 | 13 | let md = [] 14 | insertion.forEach(insertion => { 15 | md.push(insertion) 16 | }) 17 | 18 | if (!item?.subItems.includes(md[0])) { 19 | unPopulated(item.subItems, insertion) 20 | } else { 21 | return; 22 | } 23 | 24 | } 25 | 26 | if (item?.subItems.length == 0) { 27 | item.subItems = insertion 28 | } 29 | 30 | } 31 | 32 | return item 33 | 34 | } 35 | 36 | 37 | 38 | module.exports = unPopulated -------------------------------------------------------------------------------- /ca/mainPlugins/getPol.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require('axios') 2 | const chalk = require('chalk') 3 | 4 | //getGraphTokenReducedScope 5 | const fs = require('fs') 6 | const { argv } = require('yargs') 7 | const { evalLists } = require('../../plugins/evallIst') 8 | const { getGraphTokenReducedScope } = require('./tokenHandler') 9 | const v8 = require('v8') 10 | 11 | 12 | 13 | 14 | async function nTerminatedPolicyConditionsLookupFull(perm, refPol,expand) { 15 | const ret = [] 16 | var count = 0 17 | for await (let pol of perm) { 18 | //console.log(pol) 19 | const { toProcessingPipeline } = pol 20 | /* let innerPol = refPol.filter(p => p?.id !== pol?.policy?.id ) */ 21 | let innerPol = refPol 22 | 23 | for await (let indiv of toProcessingPipeline) { 24 | if (argv.debug) { 25 | 26 | let hstats = v8.getHeapStatistics() 27 | console.log((`Memory usage at: ${hstats.used_heap_size / (1024 * 1024)} for nTerminatedPolicyConditionsLookupFull`)) 28 | console.log('heap size: ', hstats.heap_size_limit / (1024 * 1024)) 29 | console.log('heap used: ', hstats.used_heap_size / (1024 * 1024)) 30 | 31 | } 32 | 33 | count++ 34 | console.log('checking main key', indiv?.rootItem, count, ' of ', toProcessingPipeline?.length) 35 | 36 | let lineage = "" 37 | let terminations = await iterPols(indiv, innerPol, pol, [], lineage) 38 | await terminations.forEach(p => ret.push(p)) 39 | } 40 | } 41 | 42 | if (!argv.allTerminations && expand) { 43 | return ret.filter(s => s?.terminated?.length == 0 || expand.find(u =>u == s?.lookup )) 44 | 45 | } 46 | 47 | if (argv.allTerminations) { 48 | return ret 49 | 50 | } 51 | return ret.filter(s => s?.terminated?.length == 0) 52 | 53 | } 54 | 55 | 56 | var cd = 0 57 | async function iterPols(item, innerPol, pol, collections, lineage) { 58 | cd++ 59 | 60 | if (cd % 100 == 0) { 61 | 62 | } 63 | 64 | lineage += `${item.rootItem} -> ` 65 | 66 | /* if (lineage.match('users:4c9ad188-5620-4523-b761-6293802db8a6')) { 67 | console.log() 68 | } */ 69 | 70 | /* console.log(lineage) */ 71 | 72 | let terminated = await evalLists(item?.rootItem, innerPol, pol?.policy) 73 | 74 | /* if (lineage.match('users:c66aad40-1dee-43c2-810b-77532250bfc8 ->')) { 75 | console.log() 76 | } */ 77 | 78 | 79 | 80 | collections.push({ 81 | policy: `all`, 82 | lineage, 83 | lookup: item?.rootItem, 84 | terminated 85 | }) 86 | 87 | if (item?.subItems?.length > 0 && terminated.length > 0) { 88 | 89 | for await (let sub of item?.subItems) { 90 | await iterPols(sub, terminated, pol, collections, lineage) 91 | } 92 | 93 | } 94 | 95 | return collections 96 | 97 | } 98 | 99 | 100 | async function TerminatedPolicyConditionsLookupFull(perm, refPol) { 101 | 102 | let termArra = [] 103 | 104 | for await (let pol of perm) { 105 | const { toProcessingPipeline } = pol 106 | 107 | // Filter the policy itself out of innerPolicies 108 | let innerPol = refPol.filter(p => p?.id !== pol?.policy?.id) 109 | 110 | for await (let indiv of toProcessingPipeline) { 111 | let sectionArray = [] 112 | 113 | 114 | 115 | let iter = await evalLists(indiv?.rootItem, innerPol, pol?.policy) 116 | 117 | 118 | 119 | 120 | for await (let sub of indiv.subItems.filter(s => s?.match(':'))) { 121 | 122 | 123 | 124 | let subTerminated = await evalLists(sub, iter, pol?.policy) 125 | sectionArray.push({ 126 | name: pol?.policy?.displayName, 127 | policy: pol?.policy, 128 | rootPermutation: indiv?.rootItem, 129 | SubPermutation: sub, 130 | terminated: subTerminated 131 | }) 132 | 133 | } 134 | 135 | if (sectionArray.length == 0) { 136 | 137 | sectionArray.push({ 138 | name: pol?.policy?.displayName, 139 | policy: pol?.policy, 140 | rootPermutation: indiv?.rootItem, 141 | SubPermutation: "no sub permutations, this is the root permutation", 142 | terminated: iter 143 | }) 144 | 145 | } 146 | 147 | termArra.push(sectionArray) 148 | } 149 | 150 | } 151 | 152 | return termArra.flat() 153 | 154 | 155 | } 156 | 157 | function TerminatedPolicyConditionsLookup(lookup, policies, filter) { 158 | 159 | if (Array.isArray(lookup)) { 160 | const matches = [] 161 | for (look of lookup) { 162 | let rootKey = Object.keys(look)[0] 163 | let subkey = Object.keys(look[rootKey])[0] 164 | let value = look[rootKey][subkey] 165 | // console.log(rootKey) 166 | matches.push(policies.filter(policy => policy?.conditions?.[rootKey]?.[subkey]?.includes(value))) 167 | 168 | } 169 | if (filter) { 170 | const filtered = [] 171 | for (look of filter) { 172 | 173 | let rootKey = Object.keys(look)[0] 174 | let subkey = Object.keys(look[rootKey])[0] 175 | let value = look[rootKey][subkey] 176 | // console.log(rootKey) 177 | matches.flat().filter(policy => !policy?.conditions?.[rootKey]?.[subkey]?.includes(value)).forEach(filt => filtered.push(filt)) 178 | } 179 | return filtered.flat() 180 | } 181 | return matches.flat() 182 | } 183 | // Plain version with single object 184 | if (typeof (lookup) == "object") { 185 | let rootKey = Object.keys(lookup)[0] 186 | let subkey = Object.keys(lookup[rootKey])[0] 187 | let value = lookup[rootKey][subkey] 188 | // console.log(rootKey) 189 | let matches = policies.filter(policy => policy?.conditions?.[rootKey]?.[subkey]?.includes(value)) 190 | console.log(matches) 191 | return matches 192 | } 193 | 194 | } 195 | 196 | 197 | function getAllExclusions(item, excluded) { 198 | 199 | 200 | if (typeof (item) === 'object' && item !== null) { 201 | 202 | 203 | Object.keys(item)?.forEach(key => { 204 | 205 | if (key.match('excl') && item[key].length > 0) { 206 | 207 | let ob = { 208 | } 209 | ob[`${key}`] = item[key] 210 | 211 | excluded.push(ob) 212 | console.log(excluded) 213 | } 214 | 215 | getAllExclusions(item[key], excluded) 216 | 217 | }) 218 | } else { 219 | return; 220 | } 221 | 222 | return excluded 223 | 224 | } 225 | 226 | 227 | 228 | function iteratePolicyKeyNonArray(keyWord, item, list) { 229 | 230 | 231 | if ((typeof (item) === 'object') && item !== null) { 232 | 233 | 234 | Object.keys(item)?.forEach(key => { 235 | 236 | if (key.match(keyWord)) { 237 | 238 | let ob = { 239 | } 240 | ob[`${key}`] = item[key] 241 | 242 | list.push(ob) 243 | //console.log(list) 244 | } 245 | 246 | iteratePolicyKeyNonArray(keyWord, item[key], list) 247 | 248 | }) 249 | } else { 250 | return; 251 | } 252 | 253 | return list 254 | 255 | } 256 | 257 | function iteratePolicyKeys(keyWord, item, list) { 258 | 259 | 260 | if (typeof (item) === 'object' && item !== null) { 261 | 262 | 263 | Object.keys(item)?.forEach(key => { 264 | 265 | if (key.match(keyWord) && item[key]?.length > 0) { 266 | 267 | let ob = { 268 | } 269 | ob[`${key}`] = item[key] 270 | 271 | list.push(ob) 272 | 273 | } 274 | 275 | iteratePolicyKeys(keyWord, item[key], list) 276 | 277 | }) 278 | } else { 279 | return; 280 | } 281 | 282 | return list 283 | 284 | } 285 | 286 | 287 | async function getPolicies() { 288 | 289 | try { 290 | return require('../../policies.json') 291 | } catch (error) { 292 | 293 | 294 | let tkn = await getGraphTokenReducedScope() 295 | 296 | let headers = { 297 | authorization: `bearer ${tkn}` 298 | } 299 | 300 | let version 301 | if (argv.forceV1) { 302 | version = "v1.0" 303 | } 304 | 305 | var opt = { 306 | url: `https://${argv.altGraph || "graph.microsoft.com"}/${version ||"beta"}/identity/conditionalAccess/policies`, 307 | headers 308 | } 309 | 310 | let { data } = await axios(opt).catch(error => { 311 | console.log(error?.response?.data || error) 312 | }) 313 | 314 | let p 315 | 316 | if (argv.includeReportOnly) { 317 | p = data?.value.filter(s => 318 | s.conditions?.devices?.deviceFilter?.mode !== 'include' 319 | && s.conditions.applications?.includeApplications.length > 0 320 | && s.grantControls?.builtInControls 321 | && s.conditions?.userRiskLevels == 0 322 | && s.conditions?.signInRiskLevels == 0 323 | ) 324 | 325 | } else { 326 | p = data?.value.filter(s => 327 | s?.state == 'enabled' 328 | && s.conditions?.devices?.deviceFilter?.mode !== 'include' 329 | && s.conditions.applications?.includeApplications.length > 0 330 | && !s.conditions.applications?.applicationFilter 331 | && s.grantControls?.builtInControls 332 | && s.conditions?.userRiskLevels == 0 333 | && s.conditions?.signInRiskLevels == 0 334 | ) 335 | } 336 | 337 | if (argv.customPolicyFilter) { 338 | p = require('../mainPlugins/customPolicyFilter')(data) 339 | } 340 | 341 | if (!argv.forceV1) { 342 | /* 343 | // New guest policies 344 | // Normalize matching new guest policies to match existing gap detection logic, when at least following conditions "internalGuest,b2bCollaborationGuest,b2bCollaborationMember" are included (and also not excluded in the same policy) 345 | 346 | */ 347 | 348 | // Handle inclusions 349 | p.filter(s => s.conditions?.users?.includeGuestsOrExternalUsers?.guestOrExternalUserTypes) 350 | .filter(s => s.conditions?.users?.includeGuestsOrExternalUsers?.guestOrExternalUserTypes.match('internalGuest') 351 | && s.conditions?.users?.includeGuestsOrExternalUsers?.guestOrExternalUserTypes.match('b2bCollaborationGuest') 352 | && s.conditions?.users?.includeGuestsOrExternalUsers?.guestOrExternalUserTypes.match('b2bCollaborationMember') 353 | && s.conditions.users?.includeGuestsOrExternalUsers.externalTenants["@odata.type"] == '#microsoft.graph.conditionalAccessAllExternalTenants' 354 | && s.conditions.users?.includeGuestsOrExternalUsers.externalTenants?.membershipKind == 'all' 355 | ).map(s => { 356 | s.conditions.users.includeUsers.push('GuestsOrExternalUsers') 357 | }) 358 | 359 | // Handle exclusions 360 | 361 | p.filter(s => s.conditions?.users?.excludeGuestsOrExternalUsers?.guestOrExternalUserTypes) 362 | .filter(s => s.conditions?.users?.excludeGuestsOrExternalUsers?.guestOrExternalUserTypes.match('internalGuest') 363 | || s.conditions?.users?.excludeGuestsOrExternalUsers?.guestOrExternalUserTypes.match('b2bCollaborationGuest') 364 | || s.conditions?.users?.excludeGuestsOrExternalUsers?.guestOrExternalUserTypes.match('b2bCollaborationMember') 365 | && s.conditions.users?.excludeGuestsOrExternalUsers.externalTenants["@odata.type"] == '#microsoft.graph.conditionalAccessAllExternalTenants' 366 | && s.conditions.users?.excludeGuestsOrExternalUsers.externalTenants?.membershipKind == 'all' 367 | ).map(s => { 368 | s.conditions.users.excludeUsers.push('GuestsOrExternalUsers') 369 | }) 370 | } 371 | 372 | 373 | 374 | // filter out device and security registration policies 375 | fs.writeFileSync('policies.json', JSON.stringify(p)) 376 | 377 | } 378 | 379 | return await getPolicies() 380 | 381 | } 382 | 383 | 384 | 385 | async function getLocations() { 386 | 387 | try { 388 | return require('../../namedLocations.json') 389 | } catch (error) { 390 | 391 | let tkn = await getGraphTokenReducedScope() 392 | 393 | let headers = { 394 | authorization: `bearer ${tkn}` 395 | } 396 | 397 | var opt = { 398 | url: `https://${argv.altGraph || "graph.microsoft.com"}/beta/conditionalAccess/namedLocations`, 399 | headers 400 | } 401 | 402 | let { data } = await axios(opt).catch(error => { 403 | console.log(error?.response?.data) 404 | }) 405 | 406 | fs.writeFileSync('namedLocations.json', JSON.stringify(data?.value)) 407 | 408 | } 409 | 410 | return await getLocations() 411 | 412 | } 413 | 414 | module.exports = { getAllExclusions, getPolicies, getLocations, TerminatedPolicyConditionsLookup, iteratePolicyKeys, TerminatedPolicyConditionsLookupFull, nTerminatedPolicyConditionsLookupFull, iteratePolicyKeyNonArray } -------------------------------------------------------------------------------- /ca/mainPlugins/getPol2.js: -------------------------------------------------------------------------------- 1 | const unPopulated = require("./findEmptySub") 2 | 3 | /* unPopulated test */ 4 | 5 | 6 | var globalExit 7 | var recursiondepth = 1 8 | var globalList = [] 9 | 10 | async function getPermutations (g) { 11 | 12 | // Sort is placed to ensure certain termination order 13 | g.sort((a,b) => { 14 | 15 | 16 | if (a.match('users:') ) { 17 | return -1 18 | } 19 | 20 | /* if (a.toLowerCase()< b.toLowerCase()) { 21 | return -1 22 | } */ 23 | 24 | 25 | 26 | /* if (a.match('clientAppTypes:') && !b.match('users:') && !b.match('locations:') ) { 27 | return -1 28 | } */ 29 | 30 | }) 31 | rcPerm(g) 32 | return globalList 33 | } 34 | 35 | 36 | function rcPerm(list, subItem) { 37 | 38 | if (globalExit) { 39 | return globalList 40 | } 41 | // new set here is redundant, (for uniqueness, but keeping this for documentation) 42 | for (let item of new Set(list.map(s => s.split(':')[0]))) { 43 | 44 | if (globalExit) { 45 | return 46 | } 47 | 48 | if (subItem) { 49 | recursiondepth++ 50 | console.log(recursiondepth) 51 | let nested = list.filter(subItem => subItem.match(item)).map(rootItem => { 52 | return { 53 | rootItem, 54 | subItems: [] 55 | } 56 | }) 57 | let outerList = list.filter(subItem => !subItem.match(item)) 58 | 59 | if (nested.length > 0 ) { 60 | if (recursiondepth == 4) { 61 | console.log() 62 | } 63 | let altSub = unPopulated(subItem,nested) 64 | /* subItem.map(ins => ins.subItems = nested) */ 65 | 66 | if (outerList.length == 0) { 67 | globalExit=true 68 | globalList=altSub 69 | return 70 | } 71 | 72 | rcPerm(outerList, altSub) 73 | 74 | 75 | 76 | } 77 | 78 | } else { 79 | if (globalExit) { 80 | return 81 | } 82 | // console.log(globalExit) 83 | let innerList = list.filter(subItem => subItem.match(item)) 84 | let outerList = list.filter(subItem => !subItem.match(item)) 85 | 86 | if (innerList.length > 0 && !globalExit) { 87 | 88 | let goForward = innerList.map(rootItem => { 89 | return { 90 | rootItem, 91 | subItems: [] 92 | } 93 | }) 94 | 95 | 96 | rcPerm(outerList, goForward) 97 | 98 | } 99 | 100 | } 101 | 102 | 103 | 104 | } 105 | 106 | return ; 107 | } 108 | 109 | 110 | module.exports={getPermutations} 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /ca/mainPlugins/graphHelpers.js: -------------------------------------------------------------------------------- 1 | const { default: axios } = require("axios") 2 | const chalk = require("chalk") 3 | const { argv } = require("yargs") 4 | const { axiosClient } = require("../../tokenHandler/axioshelpers") 5 | const { axiosClient2 } = require("./axiosh") 6 | const { newSetA } = require("./grouper") 7 | const { getGraphTokenReducedScope } = require("./tokenHandler") 8 | const waitForIt = require('util').promisify(setTimeout) 9 | 10 | 11 | async function appResolver (oids) { 12 | 13 | let token = await getGraphTokenReducedScope() 14 | let count = 0 15 | let promiseArray = [] 16 | 17 | for await (let id of new Set(oids)) { 18 | count++ 19 | console.log(count) 20 | // Throttling state 21 | if (count % 30 == 0) { 22 | await waitForIt(1000) 23 | console.log('waiting') 24 | } 25 | // Get any object in Graph by ID 26 | let opt = { 27 | url:`https://${argv.altGraph || "graph.microsoft.com"}/v1.0/serviceprincipals?$search="appId:${id}"&$select=appid,displayName,id`, 28 | headers:{ 29 | authorization: `Bearer ${token}`, 30 | ConsistencyLevel:"eventual" 31 | } 32 | } 33 | // Push objects to array that shall be resolved on later stage 34 | promiseArray.push(getRightAxiosData(opt)) 35 | 36 | 37 | } 38 | 39 | // Resolve array 40 | let respo = await Promise.all(promiseArray) 41 | // console.log(respo) 42 | // console.log('wait done') 43 | 44 | 45 | 46 | 47 | return respo?.map(s => s?.value).flat() || ['failed'] 48 | } 49 | 50 | async function objectResolver (oids) { 51 | 52 | let token = await getGraphTokenReducedScope() 53 | let count = 0 54 | let promiseArray = [] 55 | 56 | for await (let id of new Set(oids)) { 57 | count++ 58 | console.log(count) 59 | // Throttling state 60 | if (count % 30 == 0) { 61 | await waitForIt(1000) 62 | console.log('waiting') 63 | } 64 | // Get any object in Graph by ID 65 | let opt = { 66 | url:`https://${argv.altGraph || "graph.microsoft.com"}/v1.0/directoryObjects/${id}`, 67 | headers:{ 68 | authorization: `Bearer ${token}` 69 | } 70 | } 71 | // Push objects to array that shall be resolved on later stage 72 | promiseArray.push(getRightAxiosData(opt)) 73 | 74 | 75 | } 76 | 77 | // Resolve array 78 | let respo = await Promise.all(promiseArray) 79 | // console.log(respo) 80 | // console.log('wait done') 81 | 82 | 83 | 84 | 85 | return respo || ['failed'] 86 | } 87 | 88 | async function getRightAxiosData (opt) { 89 | 90 | try { 91 | let {data} = await axios(opt) 92 | return data 93 | } catch(error) { 94 | return error?.response?.data 95 | } 96 | 97 | } 98 | 99 | let groupOperations = 0 100 | async function graphListS2 (token, operation, skiptoken, responseCollector) { 101 | 102 | var options = { 103 | responseType: 'json', 104 | "method": "get", 105 | url:`https://${argv.altGraph || "graph.microsoft.com"}/v1.0/${operation}`, 106 | headers:{ 107 | 'content-type':"application/json", 108 | authorization:"bearer " + token 109 | } 110 | } 111 | 112 | let ref = operation 113 | 114 | if (skiptoken) { 115 | options.url = skiptoken 116 | } 117 | 118 | //console.log(options) 119 | 120 | if (operation.match('e051cf')) { 121 | console.log() 122 | } 123 | 124 | var data = await axiosClient2(options).catch((error) => { 125 | console.log(error?.error?.message) 126 | }) 127 | 128 | console.log(chalk.green(`getting results for groups ${operation} - previous result size:',${data.value.length}`)) 129 | 130 | if (data['@odata.nextLink']) { 131 | groupOperations++ 132 | console.log('total group calls to graph', groupOperations) 133 | console.log(chalk.green(`getting results for groups ${operation} - previous result size:',${data.value.length}`)) 134 | data.value.forEach((item) => responseCollector.push(item)) 135 | // console.log(data['@odata.nextLink']) 136 | await graphListS2(token,operation,data['@odata.nextLink'],responseCollector) 137 | 138 | } 139 | else { 140 | data.value.forEach((item) => responseCollector.push(item)) 141 | } 142 | return {ref, responseCollector:responseCollector.flat()} 143 | 144 | } 145 | 146 | 147 | 148 | async function graphListS (token, operation, skiptoken, responseCollector) { 149 | 150 | var options = { 151 | responseType: 'json', 152 | "method": "get", 153 | url:`https://${argv.altGraph || "graph.microsoft.com"}/v1.0/${operation}`, 154 | headers:{ 155 | 'content-type':"application/json", 156 | authorization:"bearer " + token 157 | } 158 | } 159 | 160 | let ref = operation 161 | 162 | if (skiptoken) { 163 | options.url = skiptoken 164 | } 165 | 166 | console.log(options) 167 | 168 | var data = await axiosClient2(options).catch((error) => { 169 | console.log(error?.error?.message) 170 | }) 171 | 172 | 173 | if (data['@odata.nextLink']) { 174 | console.log('getting results:',data.value.length) 175 | data.value.forEach((item) => responseCollector.push(item)) 176 | // console.log(data['@odata.nextLink']) 177 | await graphListS(token,operation,data['@odata.nextLink'],responseCollector) 178 | 179 | } 180 | else { 181 | data.value.forEach((item) => responseCollector.push(item)) 182 | } 183 | return {ref, responseCollector:responseCollector.flat()} 184 | 185 | } 186 | 187 | 188 | module.exports={objectResolver,graphListS,graphListS2,appResolver} -------------------------------------------------------------------------------- /ca/mainPlugins/grouper.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | function newSetWithSchema(data,a) { 8 | if (!Array.isArray(data)) { 9 | return new Error('not an array') 10 | } 11 | 12 | //let test = groupBy(data,'recommendationDisplayName') 13 | function dm (data,key) { 14 | data.map((group) =>{ 15 | let items 16 | items = groupBy(group.items, key) 17 | 18 | delete group.items 19 | group.items = [] 20 | 21 | items.sort((a,b) => { 22 | 23 | 24 | if (a.group.toLowerCase() == "high"){ 25 | return -1 26 | } 27 | if (a.group.toLowerCase() == "medium" && b.group.toLowerCase() !== "high") { 28 | return -1 29 | } 30 | 31 | 32 | }) 33 | 34 | 35 | group.items.push(items) 36 | group.items 37 | // console.log(data.length) 38 | var nextIndex = a.lastIndexOf(key)+1 39 | if (a.includes(a[nextIndex])) { 40 | dm(items,a[nextIndex]) 41 | } 42 | }) 43 | return data 44 | 45 | } 46 | var group 47 | group = groupBy(data,a[0]) 48 | if (a.length == 1 ) { 49 | return group 50 | } else { 51 | return dm(group,a[1]) 52 | } 53 | 54 | 55 | 56 | 57 | 58 | } 59 | 60 | 61 | function newSetA(data,a) { 62 | if (!Array.isArray(data)) { 63 | return new Error('not an array') 64 | } 65 | 66 | //let test = groupBy(data,'recommendationDisplayName') 67 | function dm (data,key) { 68 | data.map((group) =>{ 69 | let items 70 | items = groupBy(group.items, key) 71 | //console.log(key,group.items[0]) 72 | delete group.items 73 | group.items = [] 74 | 75 | items.sort((a,b) => { 76 | 77 | try { if (a.group.toLowerCase() < b.group.toLowerCase()) { 78 | return -1; 79 | } else { 80 | return 0 81 | }} catch (error) { 82 | error 83 | } 84 | 85 | 86 | 87 | }) 88 | 89 | 90 | group.items.push(items) 91 | group.items 92 | // console.log(data.length) 93 | var nextIndex = a.lastIndexOf(key)+1 94 | if (a.includes(a[nextIndex])) { 95 | dm(items,a[nextIndex]) 96 | } 97 | }) 98 | return data 99 | 100 | } 101 | var group 102 | group = groupBy(data,a[0]) 103 | if (a.length == 1 ) { 104 | return group 105 | } else { 106 | return dm(group,a[1]) 107 | } 108 | 109 | 110 | 111 | 112 | 113 | } 114 | 115 | 116 | 117 | function groupBy (source,property) { 118 | var categories= [] 119 | for (const item of source) { 120 | 121 | categories.includes(item?.[property]) || categories.push(item[property]) 122 | 123 | } 124 | 125 | var results = categories.map( (group,index) => { 126 | 127 | let items = source.filter((item) =>{ 128 | return item[property] == group 129 | }) 130 | 131 | items 132 | 133 | return { 134 | group, 135 | items, 136 | property 137 | } 138 | 139 | }) 140 | return results 141 | } 142 | 143 | module.exports={newSetA} -------------------------------------------------------------------------------- /ca/mainPlugins/inMemList.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | var initiated = [ 5 | "dummy-ddcd-4f5c-874d-d6adabe04cca:d02de402-dd66-44af-8c48-a3ee5c1b6596" 6 | ] 7 | 8 | function inMemoryList (items) { 9 | 10 | 11 | if(Array.isArray(items) ) { 12 | items.forEach( item => initiated.push(item)) 13 | } 14 | 15 | return initiated 16 | } 17 | 18 | module.exports={inMemoryList} -------------------------------------------------------------------------------- /ca/mainPlugins/tokenHandler.js: -------------------------------------------------------------------------------- 1 | 2 | const { exec } = require('child_process') 3 | var fs = require('fs') 4 | const { decode } = require("jsonwebtoken") 5 | var path = require('path') 6 | const { azCLI } = require('../../tokenHandler/getCode') 7 | const { rToken } = require('../../tokenHandler/refresh') 8 | const wexc = require('util').promisify(exec) 9 | const chalk = require("chalk"); 10 | const { stdout } = require('process') 11 | const { argv } = require('yargs') 12 | 13 | 14 | 15 | async function getGraphTokenReducedScope() { 16 | 17 | try { 18 | 19 | const token = require('../../tokenHandler/token.json') 20 | const decoded = decode(token) 21 | const now = Date.now().valueOf() / 1000 22 | //https://stackoverflow.com/a/55706292 (not using full verification, as the token is not meant to be validated in this tool, but in Azure API) 23 | if (typeof decoded.exp !== 'undefined' && decoded.exp < now) { 24 | throw new Error(`token expired: ${JSON.stringify(decoded)}`) 25 | } 26 | if (typeof decoded.nbf !== 'undefined' && decoded.nbf > now) { 27 | throw new Error(`token expired: ${JSON.stringify(decoded)}`) 28 | } 29 | 30 | return token 31 | 32 | } catch (error) { 33 | 34 | var token 35 | try { 36 | 37 | 38 | try { 39 | token = {} 40 | let {stdout} = await wexc(`az account get-access-token --scope=https://${argv.altGraph || "graph.microsoft.com"}/Directory.AccessAsUser.All --query accessToken --output json`) 41 | token.access_token ="" 42 | token.access_token = JSON.parse(stdout) 43 | require('fs').writeFileSync('tokenHandler/token.json',JSON.stringify(token.access_token)) 44 | token = token.access_token 45 | } catch (error) { 46 | console.log(chalk.red('no AZ CLI installed, or no existing session on AZ CLI - falling back to AZ CLI clientId')) 47 | token = await rToken() 48 | } 49 | 50 | 51 | } catch (error) { 52 | console.log(chalk.yellow('no existing session, please sign-in')) 53 | 54 | try { 55 | await azCLI() 56 | token = await rToken() 57 | } catch(error) { 58 | throw Error(error?.message) 59 | } 60 | 61 | 62 | } 63 | 64 | if (token) { 65 | 66 | } else { 67 | throw Error('failed at getting token') 68 | } 69 | 70 | return token || error 71 | 72 | } 73 | 74 | 75 | } 76 | 77 | module.exports={getGraphTokenReducedScope} -------------------------------------------------------------------------------- /ca/mainPlugins/uniqNonterm.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk") 2 | const v8 = require('v8') 3 | const { argv } = require("yargs") 4 | 5 | 6 | function nonTerm(items) { 7 | 8 | let unq = [] 9 | // let c =items.filter(f => f?.terminated.length == 0) 10 | let cd = 0 11 | items.map(s => { 12 | 13 | cd++ 14 | 15 | if (cd % 10000 == 0 && argv.debug) { 16 | console.log(`${cd} of items ${items?.length}`) 17 | let hstats = v8.getHeapStatistics() 18 | console.log((`Memory usage at: ${hstats.used_heap_size / (1024 * 1024)} for uniqNonterm.js`)) 19 | console.log('heap size: ', hstats.heap_size_limit / (1024 * 1024)) 20 | console.log('heap used: ', hstats.used_heap_size / (1024 * 1024)) 21 | } 22 | 23 | let lineage = s?.lineage.split(' -> ').filter(s => s !== '').sort((a, b) => { 24 | if (a < b) { 25 | return -1 26 | } 27 | }) 28 | 29 | let arrT = { 30 | terminated: s?.terminated?.length, 31 | lineage, 32 | policy: "All" 33 | } 34 | 35 | 36 | if (unq.includes(JSON.stringify(arrT))) { 37 | 38 | } else { 39 | unq.push(JSON.stringify(arrT)) 40 | } 41 | 42 | } 43 | 44 | ) 45 | 46 | return unq 47 | 48 | 49 | 50 | } 51 | 52 | 53 | 54 | module.exports = { nonTerm } -------------------------------------------------------------------------------- /ca/mainPlugins/userMap.js: -------------------------------------------------------------------------------- 1 | 2 | const {argv} = require("yargs") 3 | const { iteratePolicyKeys } = require("./getPol") 4 | const { objectResolver, appResolver } = require("./graphHelpers") 5 | const { newSetA } = require("./grouper") 6 | const { inMemoryList } = require("./inMemList") 7 | const waitForIt = require('util').promisify(setTimeout) 8 | // 9 | 10 | async function userMap(policies) { 11 | 12 | try { 13 | require('../../objectIds.json') 14 | require('../../appIds.json') 15 | inMemoryList(require('../../inMemoryResults.json')) 16 | console.log('cache loaded from memory') 17 | return; 18 | } catch (error) { 19 | console.log('no existing cache') 20 | } 21 | 22 | const oids = [] 23 | 24 | if (argv.expand) { 25 | 26 | } 27 | 28 | ['includeUsers', 'excludeUsers', 'includeGroups', 'excludeGroups', 'includeRoles', 'excludeRoles'].map(type => { 29 | let values = iteratePolicyKeys(type, policies, []).map(s => s?.[type]).flat() 30 | values.filter(s => s !== 'GuestsOrExternalUsers' && s !== 'All').forEach(s => oids.push(s)) 31 | return { 32 | type, 33 | values, 34 | } 35 | 36 | }) 37 | 38 | const apps = ['includeApplications', 'excludeApplications'].map(type => iteratePolicyKeys(type, policies, []).map(s => s?.[type]).flat()).flat() 39 | 40 | console.log(apps) 41 | 42 | let d = await appResolver(apps) 43 | 44 | let r = await objectResolver(oids) 45 | 46 | require('fs').writeFileSync('objectIds.json', JSON.stringify(r.flat())) 47 | require('fs').writeFileSync('appIds.json', JSON.stringify(d.flat())) 48 | 49 | let gr = newSetA(r, ['@odata.type']).filter(s => s?.group == '#microsoft.graph.group') 50 | //stable 51 | 52 | let promiseArray = [] 53 | for await (let plugin of gr) { 54 | // console.log(plugin) 55 | let pluginType = plugin.group.split('microsoft.graph.')[1] 56 | let count = 0 57 | for (let group of plugin?.items) { 58 | count++ 59 | console.log('groups handled', count, group?.displayName, group?.id) 60 | if (count % 2 == 0) { 61 | console.log('throttling main operations for groups') 62 | await waitForIt(1500) 63 | } 64 | 65 | if (group?.displayName.toLowerCase().match('all')) { 66 | console.log('sd') 67 | } 68 | 69 | let altItem = { 70 | group: "#microsoft.graph.group", 71 | items: [ 72 | group 73 | ], 74 | property: "@odata.type", 75 | } 76 | promiseArray.push(require(`./userMappers/${pluginType}.js`)(altItem)) 77 | } 78 | 79 | //Only for debugging 80 | //require('fs').writeFileSync('link.json',JSON.stringify(gr)) 81 | /* results.forEach( s => require('fs').appendFileSync('link2.txt', `${s}\r\n`)) 82 | */ 83 | 84 | 85 | 86 | } 87 | 88 | let results = await (await Promise.all(promiseArray)).flat() 89 | 90 | require('fs').writeFileSync('inMemoryResults.json', JSON.stringify(results)) 91 | 92 | 93 | inMemoryList(results) 94 | 95 | return r 96 | 97 | 98 | 99 | } 100 | 101 | module.exports = { userMap } -------------------------------------------------------------------------------- /ca/mainPlugins/userMappers/disabled_directoryRoleTemplate.js: -------------------------------------------------------------------------------- 1 | 2 | const { axiosClient } = require("../../../tokenHandler/axioshelpers") 3 | const { axiosClient2 } = require("../axiosh") 4 | const { graphListS } = require("../graphHelpers") 5 | const { getGraphTokenReducedScope } = require("../tokenHandler") 6 | const waitForIt = require('util').promisify(setTimeout) 7 | 8 | module.exports= async function (notUsed) { 9 | 10 | let token = await getGraphTokenReducedScope() 11 | 12 | let opt = { 13 | responseType: "json", 14 | method: "get", 15 | url: "https://graph.microsoft.com/v1.0/directoryRoles/", 16 | headers:{ 17 | authorization: `Bearer ${token}` 18 | } 19 | } 20 | 21 | let {value:roles} = await axiosClient2(opt).catch( error => { 22 | console.log(error) 23 | }) 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | let count = 0 32 | let promiseArray = [] 33 | 34 | for await (let item of roles) { 35 | count++ 36 | console.log(count) 37 | // Throttling state 38 | if (count % 1 == 0) { 39 | await waitForIt(1000) 40 | console.log('waiting') 41 | } 42 | 43 | promiseArray.push(graphListS(token,`directoryRoles/${item.id}/members`,undefined,[])) 44 | 45 | } 46 | 47 | 48 | let respo = await Promise.all(promiseArray) 49 | 50 | console.log(respo) 51 | 52 | 53 | // group handler 54 | } -------------------------------------------------------------------------------- /ca/mainPlugins/userMappers/group.js: -------------------------------------------------------------------------------- 1 | 2 | const { graphListS2 } = require("../graphHelpers") 3 | const { getGraphTokenReducedScope } = require("../tokenHandler") 4 | const waitForIt = require('util').promisify(setTimeout) 5 | 6 | 7 | module.exports= async function (groups) { 8 | let all = [] 9 | for await (let group of groups.items) { 10 | let depth = 0 11 | let rootGroup = group.id 12 | let respo = await IteraBl([group],[],depth) 13 | respo.flat().map( r => r.ref = rootGroup) 14 | all.push(respo.flat()) 15 | 16 | } 17 | 18 | let flatObjectsToInMem = require('../../../objectIds.json') 19 | 20 | 21 | let links = all.flat().map(item => { 22 | 23 | let itemToLink = item?.ref 24 | item.responseCollector.forEach(d => flatObjectsToInMem.push(d)) 25 | return item.responseCollector.map( r => `${itemToLink}:${r?.id}` ) 26 | 27 | }).flat() 28 | 29 | require('fs').writeFileSync('objectIds.json',JSON.stringify(flatObjectsToInMem.flat())) 30 | 31 | 32 | return links 33 | // group handler 34 | } 35 | 36 | 37 | 38 | async function IteraBl (groups, responseCollector,depth) { 39 | 40 | let iter = groups 41 | 42 | let respo = await groupHandler(iter) 43 | 44 | responseCollector.push(respo) 45 | if (depth < 1) { 46 | // limit nesting depth, as some groups can have ciruclar depedency (be members of each other) 47 | depth++ 48 | for await (let sub of respo) { 49 | 50 | let groups = sub?.responseCollector.filter(s => s?.['@odata.type'] == '#microsoft.graph.group' ) 51 | 52 | if (groups?.length > 0 ) { 53 | await IteraBl(groups,responseCollector,depth) 54 | } 55 | 56 | } 57 | 58 | } 59 | 60 | 61 | return responseCollector 62 | 63 | } 64 | 65 | 66 | var count = 0 67 | 68 | async function groupHandler (groups) { 69 | 70 | let token = await getGraphTokenReducedScope() 71 | 72 | let promiseArray = [] 73 | 74 | for await (let item of groups) { 75 | count++ 76 | //console.log(count) 77 | // Throttling state 78 | if (count % 10 == 0) { 79 | await waitForIt(1000) 80 | console.log('waiting for group resolving') 81 | } 82 | 83 | console.log(item?.id,item?.description) 84 | if (item?.id == '85d7d239-94ea-40fb-9826-6bfa47e90398') { 85 | console.log() 86 | } 87 | 88 | // Push objects to array that shall be resolved on later stage 89 | promiseArray.push(graphListS2(token,`groups/${item?.id}/members?$top=999`,undefined,[])) 90 | 91 | 92 | } 93 | 94 | // Resolve array 95 | return await Promise.all(promiseArray) 96 | 97 | } -------------------------------------------------------------------------------- /ca/permutationsPerPolicy.js: -------------------------------------------------------------------------------- 1 | 2 | // 3 | 4 | var counts = 0 5 | 6 | const global = [] 7 | var stopAfter 8 | const v8 = require('v8') 9 | const { argv } = require('yargs') 10 | 11 | 12 | 13 | function permutationList (list) { 14 | stopAfter = list.filter( s => s.match('users:')) 15 | list.sort((a,b) => { 16 | if (a.match('users:')) { 17 | return -1 18 | } 19 | }) 20 | let top = rootLister(list,global) 21 | // console.log(global) 22 | console.log('total cross condition permutations',counts) 23 | 24 | return global 25 | /* */ 26 | 27 | 28 | let filtered = top.filter(s => s?.rootItem.match('users')) 29 | 30 | return filtered 31 | } 32 | 33 | function rootLister (list,fnl) { 34 | /* list = list.filter(s => !undefined) */ 35 | for (let rootItem of list) { 36 | 37 | if (global.length == stopAfter.length) { 38 | return; 39 | 40 | } 41 | 42 | let procd = rootItem.split(':') 43 | let rootItemType = procd[0] 44 | let query = { 45 | rootItem, 46 | subItems:[procd[0]] 47 | } 48 | 49 | list.forEach( subItem => { 50 | 51 | subItemType = subItem.split(":")[0] 52 | if (!query.subItems.includes(subItem) && subItemType !== rootItemType) { 53 | query.subItems.push(subItem) 54 | } 55 | 56 | 57 | 58 | }) 59 | query.subItems = query.subItems.slice(1) 60 | if (query?.subItems.length > 0) { 61 | counts = counts+ query.subItems.length 62 | 63 | if (counts % 10000 == 0 && argv.debug) { 64 | // 65 | let hstats = v8.getHeapStatistics() 66 | console.log(counts) 67 | console.log('running key of', global.length, 'of / ', stopAfter.length) 68 | console.log((`Memory usage at: ${hstats.used_heap_size/(1024*1024)} for permutationsPerPolicy`)) 69 | console.log('heap size: ',hstats.heap_size_limit/(1024*1024)) 70 | console.log('heap used: ',hstats.used_heap_size/(1024*1024)) 71 | 72 | } 73 | 74 | 75 | let moreSub = rootLister(query.subItems,[]) 76 | query.subItems = moreSub 77 | } 78 | fnl.push(query) 79 | 80 | 81 | } 82 | return fnl 83 | } 84 | 85 | 86 | 87 | 88 | 89 | module.exports={permutationList} -------------------------------------------------------------------------------- /creatUids.js: -------------------------------------------------------------------------------- 1 | 2 | const { randomUUID } = require("crypto"); 3 | 4 | randomUUID() 5 | 6 | 7 | let existing = require('./inMemoryResults.json') 8 | 9 | for (let i = 0; i < 140000; i++) { 10 | existing.push(`${randomUUID()}:${randomUUID()}`) 11 | } 12 | 13 | console.log(existing) 14 | 15 | require('fs').writeFileSync('inMemoryResults.json',JSON.stringify(existing)) 16 | 17 | -------------------------------------------------------------------------------- /createCSVreport.js: -------------------------------------------------------------------------------- 1 | 2 | const chalk = require('chalk') 3 | const {randomUUID} = require('crypto') 4 | const { writeFileSync, appendFileSync, fstat, unlinkSync } = require('fs') 5 | const { argv } = require('yargs') 6 | const beautify = require('js-beautify').js 7 | 8 | 9 | function rpCsv (perms,cross, unparsed,filename,expandFlags) { 10 | 11 | try { 12 | unlinkSync(`${filename}.csv`) 13 | } catch(error) { 14 | console.log('') 15 | } 16 | 17 | const namedLocations = require('./namedLocations.json') 18 | var objectIdMap 19 | var appMap 20 | 21 | try { objectIdMap = require('./objectIds.json')} catch (error) { 22 | console.log('no user mapping') 23 | } 24 | 25 | try { appMap = require('./appIds.json')} catch (error) { 26 | console.log('no app mapping') 27 | } 28 | 29 | var csvReportHeader ="users,Applications,clientAppTypes,Platforms,locations,terminations, lineage \r\n" 30 | appendFileSync(`${filename}.csv`,csvReportHeader) 31 | 32 | 33 | 34 | if (unparsed) { 35 | perms = perms.map( g => JSON.parse(g)) 36 | let unTerminated = perms.filter(s => s?.terminated == 0) 37 | console.log(chalk.red(`Unterminated permutations: ${unTerminated.length}`)) 38 | } else { 39 | let unTerminated = perms.filter(s => s?.terminated == 0) 40 | console.log(chalk.red(`Unterminated permutations: ${unTerminated.length}`)) 41 | } 42 | 43 | 44 | perms.map( g => g.guid = randomUUID().replace(new RegExp('-','g',),'') ) 45 | 46 | 47 | let sorted = perms.sort((a,b) => { 48 | if ( (a.terminated?.length || a.terminated) < (b?.terminated.length || b.terminated)) { 49 | return -1 50 | } 51 | }) 52 | 53 | // console.log(sorted) 54 | let cn = 0 55 | for (let item of sorted) { 56 | 57 | cn++ 58 | if (cn % 1000 == 0) { 59 | console.log(cn, 'of /', sorted.length) 60 | } 61 | 62 | let csvBody ="" 63 | let details = item?.lineage 64 | 65 | if (JSON.stringify(details).match('users:')) { 66 | let id = details.split('users:')[1].split(' ->')[0] 67 | let resolved = objectIdMap?.find( s=> s?.id == id ) 68 | if (resolved) { 69 | 70 | if (argv.expand && expandFlags?.length > 0) { 71 | 72 | let flagged = expandFlags.find( s => s?.split('users:')[1] == id) 73 | 74 | if (flagged) { 75 | id = id.replace(id,`${resolved['@odata.type'].split('#microsoft.graph.')[1]}-${resolved?.displayName} -flagged via expand option`) 76 | } else { 77 | id = id.replace(id,`${resolved['@odata.type'].split('#microsoft.graph.')[1]}-${resolved?.displayName}`) 78 | } 79 | } else { 80 | id = id.replace(id,`${resolved['@odata.type'].split('#microsoft.graph.')[1]}-${resolved?.displayName}`) 81 | } 82 | 83 | 84 | 85 | csvBody+=`"${id}",` 86 | } else { csvBody+=`"${id}",`} 87 | } else { 88 | csvBody+="," 89 | } 90 | 91 | if (JSON.stringify(details).match('Applications:')) { 92 | let id = details.split('Applications:')[1].split(' ->')[0] 93 | let resolved = appMap?.find( s=> s?.appId == id ) 94 | if (resolved) { 95 | id = id.replace(id,resolved?.displayName) 96 | csvBody+=`"${id}",` 97 | } else { csvBody+=`"${id}",`} 98 | } else { 99 | csvBody+="," 100 | } 101 | 102 | if (JSON.stringify(details).match('clientAppTypes:')) { 103 | let id = details.split('clientAppTypes:')[1].split(' ->')[0] 104 | csvBody+=`"${id}",` 105 | } else { 106 | csvBody+="," 107 | } 108 | 109 | if (JSON.stringify(details).match('Platforms:')) { 110 | let id = details.split('Platforms:')[1].split(' ->')[0] 111 | csvBody+=`"${id}",` 112 | } else { 113 | csvBody+="," 114 | } 115 | 116 | if (JSON.stringify(details).match('Locations:')) { 117 | let id = details.split('Locations:')[1].split(' ->')[0] 118 | let resolved = namedLocations?.find( s=> s?.id == id ) 119 | if (resolved) { 120 | id = id.replace(id,resolved?.displayName) 121 | csvBody+=`"${id}",` 122 | } else { csvBody+=`"${id}",`} 123 | } else { 124 | csvBody+="," 125 | } 126 | 127 | 128 | csvBody+=`${item?.terminated?.length},` 129 | csvBody+=`"${item?.lineage}"` 130 | csvBody+="\r\n" 131 | 132 | 133 | appendFileSync(`${filename}.csv`,csvBody) 134 | 135 | 136 | 137 | 138 | } 139 | 140 | 141 | 142 | 143 | 144 | 145 | } 146 | 147 | module.exports={rpCsv} -------------------------------------------------------------------------------- /createReport.js: -------------------------------------------------------------------------------- 1 | 2 | const chalk = require('chalk') 3 | const {randomUUID} = require('crypto') 4 | const { writeFileSync, appendFileSync } = require('fs') 5 | const { argv } = require('yargs') 6 | const beautify = require('js-beautify').js 7 | 8 | 9 | function rp (perms,cross, unparsed, fileName,expandFlags) { 10 | 11 | const namedLocations = require('./namedLocations.json') 12 | var objectIdMap 13 | var appMap 14 | 15 | try { objectIdMap = require('./objectIds.json')} catch (error) { 16 | console.log('no user mapping') 17 | } 18 | 19 | try { appMap = require('./appIds.json')} catch (error) { 20 | console.log('no app mapping') 21 | } 22 | 23 | 24 | var table = `Policy | Terminations | lookup \r\n -|-|- \r\n` 25 | var details = "\r\n ## Permutations \r\n" 26 | var csvReportHeader ="users,Applications,clientAppTypes,Platforms,locations \r\n" 27 | 28 | if (unparsed) { 29 | perms = perms.map( g => JSON.parse(g)) 30 | let unTerminated = perms.filter(s => s?.terminated == 0) 31 | console.log(chalk.red(`Unterminated permutations: ${unTerminated.length}`)) 32 | } else { 33 | let unTerminated = perms.filter(s => s?.terminated == 0) 34 | console.log(chalk.red(`Unterminated permutations: ${unTerminated.length}`)) 35 | } 36 | 37 | 38 | perms.map( g => g.guid = randomUUID().replace(new RegExp('-','g',),'') ) 39 | 40 | 41 | let sorted = perms.sort((a,b) => { 42 | if ( (a.terminated?.length || a.terminated) < (b?.terminated.length || b.terminated)) { 43 | return -1 44 | } 45 | }) 46 | 47 | // console.log(sorted) 48 | 49 | let cn = 0 50 | for (let item of sorted) { 51 | cn++ 52 | if (cn % 1000 == 0) { 53 | console.log(cn, 'of /', sorted.length) 54 | } 55 | 56 | let details 57 | if (item.terminated.length == 0) { 58 | details = `${item.lineage}` 59 | } else { 60 | details = `${item.lineage}` 61 | //details = [] 62 | } 63 | 64 | if (JSON.stringify(details).match('users:') && cross && !JSON.stringify(details).match('Guests')) { 65 | 66 | let id = details.split('users:')[1].split(' ->')[0] 67 | if (id !== "All") { 68 | 69 | let resolved = objectIdMap?.find( s=> s?.id == id ) 70 | 71 | if (!resolved) { 72 | 73 | } else { 74 | 75 | if (argv.expand && expandFlags?.length > 0) { 76 | let flagged = expandFlags.find( s => s?.split('users:')[1] == id) 77 | if (flagged) { 78 | details = details.replace(id,`${resolved['@odata.type'].split('#microsoft.graph.')[1]}-${resolved?.displayName} - flagged via expand option`) 79 | } else { 80 | details = details.replace(id,`${resolved['@odata.type'].split('#microsoft.graph.')[1]}-${resolved?.displayName}`) 81 | } 82 | 83 | } else { 84 | details = details.replace(id,`${resolved['@odata.type'].split('#microsoft.graph.')[1]}-${resolved?.displayName}`) 85 | } 86 | 87 | } 88 | } 89 | 90 | } 91 | 92 | if (JSON.stringify(details).match('Applications:') && cross && appMap) { 93 | 94 | let appId = details.split('Applications:')[1].split(' ->')[0] 95 | if (appId !== "All") { 96 | 97 | let resolved = appMap?.find( s=> s?.appId == appId ) 98 | 99 | if (!resolved) { 100 | 101 | } else { 102 | details = details.replace(appId,resolved?.displayName) 103 | } 104 | } 105 | 106 | } 107 | 108 | if (JSON.stringify(details).match('Locations:') && cross ) { 109 | let id = details.split('Locations:')[1].split(' ->')[0] 110 | if (id !== "All") { 111 | 112 | let resolved = namedLocations?.find( s=> s?.id == id ) 113 | 114 | if (!resolved) { 115 | 116 | } else { 117 | details = details.replace(id,resolved?.displayName) 118 | } 119 | } 120 | } 121 | 122 | 123 | 124 | if (cross) { 125 | table+=`${item?.policy}| ${item.terminated?.length} | ${details} \r\n` 126 | } else if (unparsed) { 127 | table+=`${item?.policy}| ${item.terminated}| ${details.replace(new RegExp(',','g'),' -> ')} \r\n` 128 | } 129 | else 130 | { 131 | 132 | table+=`${item?.policy}| [${item.guid}](#${item.guid})| ${item.terminated.length || item.terminated}| ${details} \r\n` 133 | var sd = beautify(JSON.stringify(item),{ indent_size: 2, space_in_empty_paren: true }) 134 | details+="\r\n" 135 | details+="\r\n" 136 | details+=`### ${item.guid}` 137 | details+="\r\n" 138 | details+="\r\n" 139 | details+=`\`\`\`json \r\n ${sd} \r\n \`\`\`\r\n` 140 | details+="\r\n" 141 | details+="\r\n" 142 | details+="\r\n" 143 | 144 | } 145 | 146 | 147 | 148 | } 149 | 150 | if (cross) { 151 | writeFileSync(`${fileName}.md`,table) 152 | 153 | //appendFileSync('crosstable.md',details) 154 | } else { 155 | writeFileSync('table.md',table) 156 | 157 | appendFileSync('table.md',details) 158 | } 159 | 160 | 161 | 162 | 163 | } 164 | 165 | module.exports={rp} -------------------------------------------------------------------------------- /createReportLegacy.js: -------------------------------------------------------------------------------- 1 | 2 | const chalk = require('chalk') 3 | const {randomUUID} = require('crypto') 4 | const { writeFileSync, appendFileSync } = require('fs') 5 | const beautify = require('js-beautify').js 6 | 7 | 8 | function rpLegacy (perms,cross, unparsed) { 9 | 10 | const namedLocations = require('./namedLocations.json') 11 | var objectIdMap 12 | 13 | try { objectIdMap = require('./objectIds.json')} catch (error) { 14 | console.log('no user mapping') 15 | } 16 | 17 | var table = `Policy | Terminations | lookup \r\n -|-|- \r\n` 18 | var details = "\r\n ## Permutations \r\n" 19 | 20 | if (unparsed) { 21 | perms = perms.map( g => JSON.parse(g)) 22 | let unTerminated = perms.filter(s => s?.terminated == 0) 23 | console.log(chalk.red(`Unterminated permutations: ${unTerminated.length}`)) 24 | } else { 25 | let unTerminated = perms.filter(s => s?.terminated == 0) 26 | console.log(chalk.red(`Unterminated permutations: ${unTerminated.length}`)) 27 | } 28 | 29 | 30 | perms.map( g => g.guid = randomUUID().replace(new RegExp('-','g',),'') ) 31 | 32 | 33 | let sorted = perms.sort((a,b) => { 34 | if ( (a.terminated?.length || a.terminated) < (b?.terminated.length || b.terminated)) { 35 | return -1 36 | } 37 | }) 38 | 39 | // console.log(sorted) 40 | 41 | for (item of sorted) { 42 | 43 | var details 44 | if (item.terminated.length == 0) { 45 | details = `${item.lineage}` 46 | } else { 47 | details = `${item.lineage}` 48 | //details = [] 49 | } 50 | 51 | if (JSON.stringify(details).match('users') && cross && !JSON.stringify(details).match('Guests')) { 52 | 53 | let id = details.split('users:')[1] 54 | if (id !== "All") { 55 | 56 | let resolved = objectIdMap?.find( s=> s?.id == id ) 57 | 58 | if (!resolved) { 59 | 60 | } else { 61 | details = details.replace(id,resolved?.displayName) 62 | } 63 | } 64 | 65 | 66 | 67 | } 68 | 69 | if (JSON.stringify(details).match('Location') && cross ) { 70 | let id = details.split('Locations:')[1].split(',')[0] 71 | if (id !== "All") { 72 | 73 | let resolved = namedLocations?.find( s=> s?.id == id ) 74 | 75 | if (!resolved) { 76 | 77 | } else { 78 | details = details.replace(id,resolved?.displayName) 79 | } 80 | } 81 | } 82 | 83 | 84 | if (cross && !unparsed) { 85 | table+=`${item?.policy}| ${item.guid}| ${item.terminated?.length}| ${details} \r\n` 86 | } else if (unparsed) { 87 | table+=`${item?.policy}| ${item.terminated}| ${details.replace(new RegExp(',','g'),' -> ')} \r\n` 88 | } 89 | else 90 | { 91 | 92 | table+=`${item?.policy}| [${item.guid}](#${item.guid})| ${item.terminated.length || item.terminated}| ${details} \r\n` 93 | var sd = beautify(JSON.stringify(item),{ indent_size: 2, space_in_empty_paren: true }) 94 | details+="\r\n" 95 | details+="\r\n" 96 | details+=`### ${item.guid}` 97 | details+="\r\n" 98 | details+="\r\n" 99 | details+=`\`\`\`json \r\n ${sd} \r\n \`\`\`\r\n` 100 | details+="\r\n" 101 | details+="\r\n" 102 | details+="\r\n" 103 | 104 | } 105 | 106 | 107 | 108 | } 109 | 110 | if (cross) { 111 | writeFileSync('crosstable.md',table) 112 | 113 | //appendFileSync('crosstable.md',details) 114 | } else { 115 | writeFileSync('table.md',table) 116 | 117 | appendFileSync('table.md',details) 118 | } 119 | 120 | 121 | 122 | 123 | } 124 | 125 | module.exports={rpLegacy} -------------------------------------------------------------------------------- /demodata/example1-2.json: -------------------------------------------------------------------------------- 1 | [{"id":"fe06b419-5431-4e20-957e-ea53d2ece02b","displayName":"Baseline","createdDateTime":"2022-09-22T08:05:15.5985131Z","modifiedDateTime":"2022-09-22T08:23:53.825924Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"locations":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":["01ae6d6a-4b8d-4717-9db5-cb0adfa0161d"],"includeGroups":[],"excludeGroups":["a881d286-2b5c-417b-8d7e-c48b516aef83"],"includeRoles":[],"excludeRoles":["c430b396-e693-46cc-96f3-db01bf8bb62a"]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"70ca3493-2d67-42cf-9375-64d750e7a998","displayName":"Sales ","createdDateTime":"2022-09-22T08:05:41.7875617Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"locations":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":[],"excludeUsers":[],"includeGroups":["d8cf7f8a-4ca9-472c-93ce-495841688445"],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}}] -------------------------------------------------------------------------------- /demodata/extreme.json: -------------------------------------------------------------------------------- 1 | [{"id":"34a4bcd0-8005-4ff8-a03f-903e145e1c33","displayName":"baseline","createdDateTime":"2022-09-15T07:12:35.167686Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":["259fcf40-ff7c-4625-9b78-cd11793f161f","GuestsOrExternalUsers"],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"c9a2702e-7d7a-47e5-a553-143e6fe18e61","displayName":"finski","createdDateTime":"2022-09-15T07:12:35.8667436Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":["259fcf40-ff7c-4625-9b78-cd11793f161f"],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"platforms":{"includePlatforms":["all"],"excludePlatforms":["macOS"]},"locations":{"includeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"],"excludeLocations":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"eafa68cb-b498-422d-b5bc-19f73281913d","displayName":"Handle macOs","createdDateTime":"2022-09-15T07:12:36.6304774Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["browser","mobileAppsAndDesktopClients"],"servicePrincipalRiskLevels":[],"locations":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":["259fcf40-ff7c-4625-9b78-cd11793f161f"],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"platforms":{"includePlatforms":["macOS"],"excludePlatforms":["linux"]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"330891e5-2ac8-4081-a835-4385e510da4c","displayName":"CA005: Require multifactor authentication for guest access","createdDateTime":"2022-09-15T07:12:37.3079829Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"locations":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":["88cc92be-d474-4d95-a57d-7b3ef701f510"],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["GuestsOrExternalUsers"],"excludeUsers":["259fcf40-ff7c-4625-9b78-cd11793f161f"],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"8a2d5f82-50ee-4699-afc6-66630f2afc22","displayName":"Handle Office 365","createdDateTime":"2022-09-15T07:12:38.2730818Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["Office365"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"]}},"grantControls":{"operator":"OR","builtInControls":["compliantDevice"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"e6b8c79a-3787-4c2e-8f2f-668fb7ee4c17","displayName":"Guests handle rare API","createdDateTime":"2022-09-15T07:12:38.9762264Z","modifiedDateTime":"2022-09-15T07:42:29.3893722Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["mobileAppsAndDesktopClients"],"servicePrincipalRiskLevels":[],"platforms":null,"locations":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["88cc92be-d474-4d95-a57d-7b3ef701f510"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["GuestsOrExternalUsers"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}}] -------------------------------------------------------------------------------- /demodata/extremelyNarrow.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "ef679df2-01b8-462e-9416-c2523f0af464", 4 | "displayName": "baseline", 5 | "createdDateTime": "2022-09-15T06:10:38.2298929Z", 6 | "modifiedDateTime": "2022-09-15T06:26:20.3720207Z", 7 | "state": "enabled", 8 | "sessionControls": null, 9 | "conditions": { 10 | "userRiskLevels": [], 11 | "signInRiskLevels": [], 12 | "clientAppTypes": [ 13 | "all" 14 | ], 15 | "servicePrincipalRiskLevels": [], 16 | "platforms": null, 17 | "devices": null, 18 | "clientApplications": null, 19 | "applications": { 20 | "includeApplications": [ 21 | "All" 22 | ], 23 | "excludeApplications": [], 24 | "includeUserActions": [], 25 | "includeAuthenticationContextClassReferences": [] 26 | }, 27 | "users": { 28 | "includeUsers": [ 29 | "All" 30 | ], 31 | "excludeUsers": [ 32 | "259fcf40-ff7c-4625-9b78-cd11793f161f", 33 | "GuestsOrExternalUsers" 34 | ], 35 | "includeGroups": [], 36 | "excludeGroups": [], 37 | "includeRoles": [], 38 | "excludeRoles": [] 39 | }, 40 | "locations": { 41 | "includeLocations": [ 42 | "All" 43 | ], 44 | "excludeLocations": [ 45 | "10a25087-9bf6-479b-a2a1-37e814310c90" 46 | ] 47 | } 48 | }, 49 | "grantControls": { 50 | "operator": "OR", 51 | "builtInControls": [ 52 | "mfa" 53 | ], 54 | "customAuthenticationFactors": [], 55 | "termsOfUse": [] 56 | } 57 | }, 58 | { 59 | "id": "28a28af3-9878-479e-b10e-a0cbf682d413", 60 | "displayName": "finski", 61 | "createdDateTime": "2022-09-15T06:10:38.9417468Z", 62 | "modifiedDateTime": "2022-09-15T06:31:28.688049Z", 63 | "state": "enabled", 64 | "sessionControls": null, 65 | "conditions": { 66 | "userRiskLevels": [], 67 | "signInRiskLevels": [], 68 | "clientAppTypes": [ 69 | "all" 70 | ], 71 | "servicePrincipalRiskLevels": [], 72 | "devices": null, 73 | "clientApplications": null, 74 | "applications": { 75 | "includeApplications": [ 76 | "All" 77 | ], 78 | "excludeApplications": [], 79 | "includeUserActions": [], 80 | "includeAuthenticationContextClassReferences": [] 81 | }, 82 | "users": { 83 | "includeUsers": [ 84 | "All" 85 | ], 86 | "excludeUsers": [ 87 | "259fcf40-ff7c-4625-9b78-cd11793f161f" 88 | ], 89 | "includeGroups": [], 90 | "excludeGroups": [], 91 | "includeRoles": [], 92 | "excludeRoles": [] 93 | }, 94 | "platforms": { 95 | "includePlatforms": [ 96 | "all" 97 | ], 98 | "excludePlatforms": [ 99 | "macOS" 100 | ] 101 | }, 102 | "locations": { 103 | "includeLocations": [ 104 | "10a25087-9bf6-479b-a2a1-37e814310c90" 105 | ], 106 | "excludeLocations": [] 107 | } 108 | }, 109 | "grantControls": { 110 | "operator": "OR", 111 | "builtInControls": [ 112 | "mfa" 113 | ], 114 | "customAuthenticationFactors": [], 115 | "termsOfUse": [] 116 | } 117 | }, 118 | { 119 | "id": "93264357-9b96-4933-afd0-45d88efc9c50", 120 | "displayName": "Handle macOs", 121 | "createdDateTime": "2022-09-15T06:10:39.6247428Z", 122 | "modifiedDateTime": "2022-09-15T06:35:45.8500554Z", 123 | "state": "enabled", 124 | "sessionControls": null, 125 | "conditions": { 126 | "userRiskLevels": [], 127 | "signInRiskLevels": [], 128 | "clientAppTypes": [ 129 | "browser", 130 | "mobileAppsAndDesktopClients" 131 | ], 132 | "servicePrincipalRiskLevels": [], 133 | "locations": null, 134 | "devices": null, 135 | "clientApplications": null, 136 | "applications": { 137 | "includeApplications": [ 138 | "All" 139 | ], 140 | "excludeApplications": [], 141 | "includeUserActions": [], 142 | "includeAuthenticationContextClassReferences": [] 143 | }, 144 | "users": { 145 | "includeUsers": [ 146 | "All" 147 | ], 148 | "excludeUsers": [ 149 | "259fcf40-ff7c-4625-9b78-cd11793f161f" 150 | ], 151 | "includeGroups": [], 152 | "excludeGroups": [], 153 | "includeRoles": [], 154 | "excludeRoles": [] 155 | }, 156 | "platforms": { 157 | "includePlatforms": [ 158 | "macOS" 159 | ], 160 | "excludePlatforms": [ 161 | "linux" 162 | ] 163 | } 164 | }, 165 | "grantControls": { 166 | "operator": "OR", 167 | "builtInControls": [ 168 | "mfa" 169 | ], 170 | "customAuthenticationFactors": [], 171 | "termsOfUse": [] 172 | } 173 | }, 174 | { 175 | "id": "77b0196d-2c3d-4efc-b147-f84e7ac10066", 176 | "displayName": "CA005: Require multifactor authentication for guest access", 177 | "createdDateTime": "2022-09-15T06:28:48.8020453Z", 178 | "modifiedDateTime": "2022-09-15T06:29:41.0451632Z", 179 | "state": "enabled", 180 | "sessionControls": null, 181 | "conditions": { 182 | "userRiskLevels": [], 183 | "signInRiskLevels": [], 184 | "clientAppTypes": [ 185 | "all" 186 | ], 187 | "servicePrincipalRiskLevels": [], 188 | "platforms": null, 189 | "locations": null, 190 | "devices": null, 191 | "clientApplications": null, 192 | "applications": { 193 | "includeApplications": [ 194 | "All" 195 | ], 196 | "excludeApplications": [ 197 | "Office365" 198 | ], 199 | "includeUserActions": [], 200 | "includeAuthenticationContextClassReferences": [] 201 | }, 202 | "users": { 203 | "includeUsers": [ 204 | "GuestsOrExternalUsers" 205 | ], 206 | "excludeUsers": [ 207 | "259fcf40-ff7c-4625-9b78-cd11793f161f" 208 | ], 209 | "includeGroups": [], 210 | "excludeGroups": [], 211 | "includeRoles": [], 212 | "excludeRoles": [] 213 | } 214 | }, 215 | "grantControls": { 216 | "operator": "OR", 217 | "builtInControls": [ 218 | "mfa" 219 | ], 220 | "customAuthenticationFactors": [], 221 | "termsOfUse": [] 222 | } 223 | }, 224 | { 225 | "id": "8d8dd620-da93-4be8-a043-fad7c5a54241", 226 | "displayName": "Handle Office 365", 227 | "createdDateTime": "2022-09-15T06:31:02.2808194Z", 228 | "modifiedDateTime": null, 229 | "state": "enabled", 230 | "sessionControls": null, 231 | "conditions": { 232 | "userRiskLevels": [], 233 | "signInRiskLevels": [], 234 | "clientAppTypes": [ 235 | "all" 236 | ], 237 | "servicePrincipalRiskLevels": [], 238 | "platforms": null, 239 | "devices": null, 240 | "clientApplications": null, 241 | "applications": { 242 | "includeApplications": [ 243 | "Office365" 244 | ], 245 | "excludeApplications": [], 246 | "includeUserActions": [], 247 | "includeAuthenticationContextClassReferences": [] 248 | }, 249 | "users": { 250 | "includeUsers": [ 251 | "All" 252 | ], 253 | "excludeUsers": [ 254 | "GuestsOrExternalUsers" 255 | ], 256 | "includeGroups": [], 257 | "excludeGroups": [], 258 | "includeRoles": [], 259 | "excludeRoles": [] 260 | }, 261 | "locations": { 262 | "includeLocations": [ 263 | "All" 264 | ], 265 | "excludeLocations": [ 266 | "10a25087-9bf6-479b-a2a1-37e814310c90" 267 | ] 268 | } 269 | }, 270 | "grantControls": { 271 | "operator": "OR", 272 | "builtInControls": [ 273 | "compliantDevice" 274 | ], 275 | "customAuthenticationFactors": [], 276 | "termsOfUse": [] 277 | } 278 | }, 279 | { 280 | "id": "7bd803c4-31c8-49ae-884f-c92a243331cb", 281 | "displayName": "Guests handle O365", 282 | "createdDateTime": "2022-09-15T06:34:15.9217336Z", 283 | "modifiedDateTime": null, 284 | "state": "enabled", 285 | "sessionControls": null, 286 | "conditions": { 287 | "userRiskLevels": [], 288 | "signInRiskLevels": [], 289 | "clientAppTypes": [ 290 | "mobileAppsAndDesktopClients" 291 | ], 292 | "servicePrincipalRiskLevels": [], 293 | "platforms": null, 294 | "locations": null, 295 | "devices": null, 296 | "clientApplications": null, 297 | "applications": { 298 | "includeApplications": [ 299 | "Office365" 300 | ], 301 | "excludeApplications": [], 302 | "includeUserActions": [], 303 | "includeAuthenticationContextClassReferences": [] 304 | }, 305 | "users": { 306 | "includeUsers": [ 307 | "GuestsOrExternalUsers" 308 | ], 309 | "excludeUsers": [], 310 | "includeGroups": [], 311 | "excludeGroups": [], 312 | "includeRoles": [], 313 | "excludeRoles": [] 314 | } 315 | }, 316 | "grantControls": { 317 | "operator": "OR", 318 | "builtInControls": [ 319 | "mfa" 320 | ], 321 | "customAuthenticationFactors": [], 322 | "termsOfUse": [] 323 | } 324 | } 325 | ] -------------------------------------------------------------------------------- /demodata/extremelyNarrow2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "ef679df2-01b8-462e-9416-c2523f0af464", 4 | "displayName": "baseline", 5 | "createdDateTime": "2022-09-15T06:10:38.2298929Z", 6 | "modifiedDateTime": "2022-09-15T06:26:20.3720207Z", 7 | "state": "enabled", 8 | "sessionControls": null, 9 | "conditions": { 10 | "userRiskLevels": [], 11 | "signInRiskLevels": [], 12 | "clientAppTypes": [ 13 | "all" 14 | ], 15 | "servicePrincipalRiskLevels": [], 16 | "platforms": null, 17 | "devices": null, 18 | "clientApplications": null, 19 | "applications": { 20 | "includeApplications": [ 21 | "All" 22 | ], 23 | "excludeApplications": [], 24 | "includeUserActions": [], 25 | "includeAuthenticationContextClassReferences": [] 26 | }, 27 | "users": { 28 | "includeUsers": [ 29 | "All" 30 | ], 31 | "excludeUsers": [ 32 | "259fcf40-ff7c-4625-9b78-cd11793f161f", 33 | "GuestsOrExternalUsers" 34 | ], 35 | "includeGroups": [], 36 | "excludeGroups": [], 37 | "includeRoles": [], 38 | "excludeRoles": [] 39 | }, 40 | "locations": { 41 | "includeLocations": [ 42 | "All" 43 | ], 44 | "excludeLocations": [ 45 | "10a25087-9bf6-479b-a2a1-37e814310c90" 46 | ] 47 | } 48 | }, 49 | "grantControls": { 50 | "operator": "OR", 51 | "builtInControls": [ 52 | "mfa" 53 | ], 54 | "customAuthenticationFactors": [], 55 | "termsOfUse": [] 56 | } 57 | }, 58 | { 59 | "id": "28a28af3-9878-479e-b10e-a0cbf682d413", 60 | "displayName": "finski", 61 | "createdDateTime": "2022-09-15T06:10:38.9417468Z", 62 | "modifiedDateTime": "2022-09-15T06:31:28.688049Z", 63 | "state": "enabled", 64 | "sessionControls": null, 65 | "conditions": { 66 | "userRiskLevels": [], 67 | "signInRiskLevels": [], 68 | "clientAppTypes": [ 69 | "all" 70 | ], 71 | "servicePrincipalRiskLevels": [], 72 | "devices": null, 73 | "clientApplications": null, 74 | "applications": { 75 | "includeApplications": [ 76 | "All" 77 | ], 78 | "excludeApplications": [], 79 | "includeUserActions": [], 80 | "includeAuthenticationContextClassReferences": [] 81 | }, 82 | "users": { 83 | "includeUsers": [ 84 | "All" 85 | ], 86 | "excludeUsers": [ 87 | "259fcf40-ff7c-4625-9b78-cd11793f161f" 88 | ], 89 | "includeGroups": [], 90 | "excludeGroups": [], 91 | "includeRoles": [], 92 | "excludeRoles": [] 93 | }, 94 | "platforms": { 95 | "includePlatforms": [ 96 | "all" 97 | ], 98 | "excludePlatforms": [ 99 | "macOS" 100 | ] 101 | }, 102 | "locations": { 103 | "includeLocations": [ 104 | "10a25087-9bf6-479b-a2a1-37e814310c90" 105 | ], 106 | "excludeLocations": [] 107 | } 108 | }, 109 | "grantControls": { 110 | "operator": "OR", 111 | "builtInControls": [ 112 | "mfa" 113 | ], 114 | "customAuthenticationFactors": [], 115 | "termsOfUse": [] 116 | } 117 | }, 118 | { 119 | "id": "93264357-9b96-4933-afd0-45d88efc9c50", 120 | "displayName": "Handle macOs", 121 | "createdDateTime": "2022-09-15T06:10:39.6247428Z", 122 | "modifiedDateTime": "2022-09-15T06:35:45.8500554Z", 123 | "state": "enabled", 124 | "sessionControls": null, 125 | "conditions": { 126 | "userRiskLevels": [], 127 | "signInRiskLevels": [], 128 | "clientAppTypes": [ 129 | "browser", 130 | "mobileAppsAndDesktopClients" 131 | ], 132 | "servicePrincipalRiskLevels": [], 133 | "locations": null, 134 | "devices": null, 135 | "clientApplications": null, 136 | "applications": { 137 | "includeApplications": [ 138 | "All" 139 | ], 140 | "excludeApplications": [], 141 | "includeUserActions": [], 142 | "includeAuthenticationContextClassReferences": [] 143 | }, 144 | "users": { 145 | "includeUsers": [ 146 | "All" 147 | ], 148 | "excludeUsers": [ 149 | "259fcf40-ff7c-4625-9b78-cd11793f161f" 150 | ], 151 | "includeGroups": [], 152 | "excludeGroups": [], 153 | "includeRoles": [], 154 | "excludeRoles": [] 155 | }, 156 | "platforms": { 157 | "includePlatforms": [ 158 | "macOS" 159 | ], 160 | "excludePlatforms": [ 161 | "linux" 162 | ] 163 | } 164 | }, 165 | "grantControls": { 166 | "operator": "OR", 167 | "builtInControls": [ 168 | "mfa" 169 | ], 170 | "customAuthenticationFactors": [], 171 | "termsOfUse": [] 172 | } 173 | }, 174 | { 175 | "id": "77b0196d-2c3d-4efc-b147-f84e7ac10066", 176 | "displayName": "CA005: Require multifactor authentication for guest access", 177 | "createdDateTime": "2022-09-15T06:28:48.8020453Z", 178 | "modifiedDateTime": "2022-09-15T06:59:48.8188813Z", 179 | "state": "enabled", 180 | "sessionControls": null, 181 | "conditions": { 182 | "userRiskLevels": [], 183 | "signInRiskLevels": [], 184 | "clientAppTypes": [ 185 | "all" 186 | ], 187 | "servicePrincipalRiskLevels": [], 188 | "platforms": null, 189 | "locations": null, 190 | "devices": null, 191 | "clientApplications": null, 192 | "applications": { 193 | "includeApplications": [ 194 | "All" 195 | ], 196 | "excludeApplications": [ 197 | "88cc92be-d474-4d95-a57d-7b3ef701f510" 198 | ], 199 | "includeUserActions": [], 200 | "includeAuthenticationContextClassReferences": [] 201 | }, 202 | "users": { 203 | "includeUsers": [ 204 | "GuestsOrExternalUsers" 205 | ], 206 | "excludeUsers": [ 207 | "259fcf40-ff7c-4625-9b78-cd11793f161f" 208 | ], 209 | "includeGroups": [], 210 | "excludeGroups": [], 211 | "includeRoles": [], 212 | "excludeRoles": [] 213 | } 214 | }, 215 | "grantControls": { 216 | "operator": "OR", 217 | "builtInControls": [ 218 | "mfa" 219 | ], 220 | "customAuthenticationFactors": [], 221 | "termsOfUse": [] 222 | } 223 | }, 224 | { 225 | "id": "8d8dd620-da93-4be8-a043-fad7c5a54241", 226 | "displayName": "Handle Office 365", 227 | "createdDateTime": "2022-09-15T06:31:02.2808194Z", 228 | "modifiedDateTime": "2022-09-15T06:56:04.4992566Z", 229 | "state": "enabled", 230 | "sessionControls": null, 231 | "conditions": { 232 | "userRiskLevels": [], 233 | "signInRiskLevels": [], 234 | "clientAppTypes": [ 235 | "all" 236 | ], 237 | "servicePrincipalRiskLevels": [], 238 | "platforms": null, 239 | "devices": null, 240 | "clientApplications": null, 241 | "applications": { 242 | "includeApplications": [ 243 | "Office365" 244 | ], 245 | "excludeApplications": [], 246 | "includeUserActions": [], 247 | "includeAuthenticationContextClassReferences": [] 248 | }, 249 | "users": { 250 | "includeUsers": [ 251 | "All" 252 | ], 253 | "excludeUsers": [], 254 | "includeGroups": [], 255 | "excludeGroups": [], 256 | "includeRoles": [], 257 | "excludeRoles": [] 258 | }, 259 | "locations": { 260 | "includeLocations": [ 261 | "All" 262 | ], 263 | "excludeLocations": [ 264 | "10a25087-9bf6-479b-a2a1-37e814310c90" 265 | ] 266 | } 267 | }, 268 | "grantControls": { 269 | "operator": "OR", 270 | "builtInControls": [ 271 | "compliantDevice" 272 | ], 273 | "customAuthenticationFactors": [], 274 | "termsOfUse": [] 275 | } 276 | }, 277 | { 278 | "id": "7bd803c4-31c8-49ae-884f-c92a243331cb", 279 | "displayName": "Guests handle O365", 280 | "createdDateTime": "2022-09-15T06:34:15.9217336Z", 281 | "modifiedDateTime": "2022-09-15T06:56:18.9302211Z", 282 | "state": "enabled", 283 | "sessionControls": null, 284 | "conditions": { 285 | "userRiskLevels": [], 286 | "signInRiskLevels": [], 287 | "clientAppTypes": [ 288 | "mobileAppsAndDesktopClients" 289 | ], 290 | "servicePrincipalRiskLevels": [], 291 | "platforms": null, 292 | "locations": null, 293 | "devices": null, 294 | "clientApplications": null, 295 | "applications": { 296 | "includeApplications": [ 297 | "88cc92be-d474-4d95-a57d-7b3ef701f510" 298 | ], 299 | "excludeApplications": [], 300 | "includeUserActions": [], 301 | "includeAuthenticationContextClassReferences": [] 302 | }, 303 | "users": { 304 | "includeUsers": [ 305 | "GuestsOrExternalUsers" 306 | ], 307 | "excludeUsers": [], 308 | "includeGroups": [], 309 | "excludeGroups": [], 310 | "includeRoles": [], 311 | "excludeRoles": [] 312 | } 313 | }, 314 | "grantControls": { 315 | "operator": "OR", 316 | "builtInControls": [ 317 | "mfa" 318 | ], 319 | "customAuthenticationFactors": [], 320 | "termsOfUse": [] 321 | } 322 | } 323 | ] -------------------------------------------------------------------------------- /demodata/largePol.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "ab78ce77-fbd9-4800-a628-785f4b093451", 4 | "displayName": "mgmtApiReader", 5 | "createdDateTime": "2022-09-27T06:05:09.1632958Z", 6 | "modifiedDateTime": null, 7 | "state": "enabled", 8 | "sessionControls": null, 9 | "conditions": { 10 | "userRiskLevels": [], 11 | "signInRiskLevels": [], 12 | "clientAppTypes": [ 13 | "all" 14 | ], 15 | "servicePrincipalRiskLevels": [], 16 | "platforms": null, 17 | "devices": null, 18 | "clientApplications": null, 19 | "applications": { 20 | "includeApplications": [ 21 | "ae1ece49-cc61-4d08-86c2-b0246abc3f6a" 22 | ], 23 | "excludeApplications": [], 24 | "includeUserActions": [], 25 | "includeAuthenticationContextClassReferences": [] 26 | }, 27 | "users": { 28 | "includeUsers": [ 29 | "All" 30 | ], 31 | "excludeUsers": [], 32 | "includeGroups": [], 33 | "excludeGroups": [], 34 | "includeRoles": [], 35 | "excludeRoles": [] 36 | }, 37 | "locations": { 38 | "includeLocations": [ 39 | "All" 40 | ], 41 | "excludeLocations": [ 42 | "10a25087-9bf6-479b-a2a1-37e814310c90" 43 | ] 44 | } 45 | }, 46 | "grantControls": { 47 | "operator": "OR", 48 | "builtInControls": [ 49 | "mfa" 50 | ], 51 | "customAuthenticationFactors": [], 52 | "termsOfUse": [] 53 | } 54 | }, 55 | { 56 | "id": "5208359c-2e81-4458-9173-b79925c1d71f", 57 | "displayName": "sda3", 58 | "createdDateTime": "2022-09-27T06:05:09.9937354Z", 59 | "modifiedDateTime": null, 60 | "state": "enabled", 61 | "sessionControls": null, 62 | "conditions": { 63 | "userRiskLevels": [], 64 | "signInRiskLevels": [], 65 | "clientAppTypes": [ 66 | "all" 67 | ], 68 | "servicePrincipalRiskLevels": [], 69 | "platforms": null, 70 | "devices": null, 71 | "clientApplications": null, 72 | "applications": { 73 | "includeApplications": [ 74 | "8d062ed1-bb17-465c-b02f-7877cae54483" 75 | ], 76 | "excludeApplications": [], 77 | "includeUserActions": [], 78 | "includeAuthenticationContextClassReferences": [] 79 | }, 80 | "users": { 81 | "includeUsers": [ 82 | "All" 83 | ], 84 | "excludeUsers": [], 85 | "includeGroups": [], 86 | "excludeGroups": [], 87 | "includeRoles": [], 88 | "excludeRoles": [] 89 | }, 90 | "locations": { 91 | "includeLocations": [ 92 | "All" 93 | ], 94 | "excludeLocations": [ 95 | "10a25087-9bf6-479b-a2a1-37e814310c90" 96 | ] 97 | } 98 | }, 99 | "grantControls": { 100 | "operator": "OR", 101 | "builtInControls": [ 102 | "mfa" 103 | ], 104 | "customAuthenticationFactors": [], 105 | "termsOfUse": [] 106 | } 107 | }, 108 | { 109 | "id": "554e9c87-6084-46db-87a3-f1977fa68e73", 110 | "displayName": "api-1313", 111 | "createdDateTime": "2022-09-27T06:05:10.7079771Z", 112 | "modifiedDateTime": null, 113 | "state": "enabled", 114 | "sessionControls": null, 115 | "conditions": { 116 | "userRiskLevels": [], 117 | "signInRiskLevels": [], 118 | "clientAppTypes": [ 119 | "all" 120 | ], 121 | "servicePrincipalRiskLevels": [], 122 | "platforms": null, 123 | "devices": null, 124 | "clientApplications": null, 125 | "applications": { 126 | "includeApplications": [ 127 | "88cc92be-d474-4d95-a57d-7b3ef701f510" 128 | ], 129 | "excludeApplications": [], 130 | "includeUserActions": [], 131 | "includeAuthenticationContextClassReferences": [] 132 | }, 133 | "users": { 134 | "includeUsers": [ 135 | "All" 136 | ], 137 | "excludeUsers": [], 138 | "includeGroups": [], 139 | "excludeGroups": [], 140 | "includeRoles": [], 141 | "excludeRoles": [] 142 | }, 143 | "locations": { 144 | "includeLocations": [ 145 | "All" 146 | ], 147 | "excludeLocations": [ 148 | "10a25087-9bf6-479b-a2a1-37e814310c90" 149 | ] 150 | } 151 | }, 152 | "grantControls": { 153 | "operator": "OR", 154 | "builtInControls": [ 155 | "mfa" 156 | ], 157 | "customAuthenticationFactors": [], 158 | "termsOfUse": [] 159 | } 160 | }, 161 | { 162 | "id": "9039f97e-3738-4ada-9e41-36a0662bbeb0", 163 | "displayName": "spa_cif", 164 | "createdDateTime": "2022-09-27T06:05:11.6799231Z", 165 | "modifiedDateTime": null, 166 | "state": "enabled", 167 | "sessionControls": null, 168 | "conditions": { 169 | "userRiskLevels": [], 170 | "signInRiskLevels": [], 171 | "clientAppTypes": [ 172 | "all" 173 | ], 174 | "servicePrincipalRiskLevels": [], 175 | "platforms": null, 176 | "devices": null, 177 | "clientApplications": null, 178 | "applications": { 179 | "includeApplications": [ 180 | "9e78a010-6344-4862-84eb-822360016f6a" 181 | ], 182 | "excludeApplications": [], 183 | "includeUserActions": [], 184 | "includeAuthenticationContextClassReferences": [] 185 | }, 186 | "users": { 187 | "includeUsers": [ 188 | "All" 189 | ], 190 | "excludeUsers": [], 191 | "includeGroups": [], 192 | "excludeGroups": [], 193 | "includeRoles": [], 194 | "excludeRoles": [] 195 | }, 196 | "locations": { 197 | "includeLocations": [ 198 | "All" 199 | ], 200 | "excludeLocations": [ 201 | "10a25087-9bf6-479b-a2a1-37e814310c90" 202 | ] 203 | } 204 | }, 205 | "grantControls": { 206 | "operator": "OR", 207 | "builtInControls": [ 208 | "mfa" 209 | ], 210 | "customAuthenticationFactors": [], 211 | "termsOfUse": [] 212 | } 213 | }, 214 | { 215 | "id": "0adeace5-eb6c-4b5d-a029-bf4c270477ed", 216 | "displayName": "api-14736", 217 | "createdDateTime": "2022-09-27T06:05:15.008302Z", 218 | "modifiedDateTime": null, 219 | "state": "enabled", 220 | "sessionControls": null, 221 | "conditions": { 222 | "userRiskLevels": [], 223 | "signInRiskLevels": [], 224 | "clientAppTypes": [ 225 | "all" 226 | ], 227 | "servicePrincipalRiskLevels": [], 228 | "platforms": null, 229 | "devices": null, 230 | "clientApplications": null, 231 | "applications": { 232 | "includeApplications": [ 233 | "881b5d33-671a-43b9-8fd1-ba1e446f607f" 234 | ], 235 | "excludeApplications": [], 236 | "includeUserActions": [], 237 | "includeAuthenticationContextClassReferences": [] 238 | }, 239 | "users": { 240 | "includeUsers": [ 241 | "All" 242 | ], 243 | "excludeUsers": [], 244 | "includeGroups": [], 245 | "excludeGroups": [], 246 | "includeRoles": [], 247 | "excludeRoles": [] 248 | }, 249 | "locations": { 250 | "includeLocations": [ 251 | "All" 252 | ], 253 | "excludeLocations": [ 254 | "10a25087-9bf6-479b-a2a1-37e814310c90" 255 | ] 256 | } 257 | }, 258 | "grantControls": { 259 | "operator": "OR", 260 | "builtInControls": [ 261 | "mfa" 262 | ], 263 | "customAuthenticationFactors": [], 264 | "termsOfUse": [] 265 | } 266 | }, 267 | { 268 | "id": "b47e73ee-cb35-48b7-a3af-a864f72f7400", 269 | "displayName": "baseline", 270 | "createdDateTime": "2022-09-27T06:05:15.8243981Z", 271 | "modifiedDateTime": null, 272 | "state": "enabled", 273 | "sessionControls": null, 274 | "conditions": { 275 | "userRiskLevels": [], 276 | "signInRiskLevels": [], 277 | "clientAppTypes": [ 278 | "all" 279 | ], 280 | "servicePrincipalRiskLevels": [], 281 | "platforms": null, 282 | "devices": null, 283 | "clientApplications": null, 284 | "applications": { 285 | "includeApplications": [ 286 | "All" 287 | ], 288 | "excludeApplications": [ 289 | "ae1ece49-cc61-4d08-86c2-b0246abc3f6a", 290 | "8d062ed1-bb17-465c-b02f-7877cae54483", 291 | "88cc92be-d474-4d95-a57d-7b3ef701f510", 292 | "9e78a010-6344-4862-84eb-822360016f6a" 293 | ], 294 | "includeUserActions": [], 295 | "includeAuthenticationContextClassReferences": [] 296 | }, 297 | "users": { 298 | "includeUsers": [ 299 | "All" 300 | ], 301 | "excludeUsers": [ 302 | "d8bfa72d-e26f-43cc-a597-bc0a21b1575f", 303 | "433f4482-8aab-4ceb-bc13-2ddfb8941986", 304 | "d0846dbe-e80c-433d-957c-cbcb9f4cbaaa", 305 | "3e845433-ec5f-4209-909f-00b118c133bd", 306 | "d02fce95-d752-4ac8-9ca9-8c15cba1a26c", 307 | "175a4a60-dae2-4871-9a18-eb497a8868ec", 308 | "4645297d-0032-44b3-976c-c9e69d99f3c0", 309 | "e75ef692-198b-4218-8d06-5ac5ca225d35", 310 | "f909c7b7-7e03-42d3-84a9-b0189322eb99", 311 | "71207ae7-f02a-4079-9f22-fb204432c7d6", 312 | "ed648df1-6ab3-4a23-89cd-e85e08097c82", 313 | "730a89e4-6934-4f3b-b75e-0b28cb943995", 314 | "f426953f-773c-4ea3-a582-8f35f2e694a3", 315 | "fea0612e-cfde-4b32-bdd4-e3273b969c0a", 316 | "2617eadf-9b50-471a-8d3b-899e89f99cd0", 317 | "df7e1cef-69d0-46fd-afaa-ed40b6360084", 318 | "82e8600a-a38b-48c7-a4d1-07dfd8491f96", 319 | "6d8499d0-340c-4e9c-848a-a146174f7c63", 320 | "711d918e-c2d0-4e6b-9f72-de26327d85ae", 321 | "ac9944c2-aee5-406b-a168-b6de268b018e", 322 | "0fb0e2ed-e7d0-4edf-94c4-0375b185f73c", 323 | "ac7a0055-54d6-477e-9145-9ac42d83b423", 324 | "e42a481b-be70-4476-b189-02dd87f9a686", 325 | "0a69ce8e-d569-44c2-905f-9b36d9674ae9", 326 | "2336d1ce-4c80-4305-b59a-901794fd91d6", 327 | "fb7c8be4-53a4-4093-8d9a-9edd3020bdd0", 328 | "01ae6d6a-4b8d-4717-9db5-cb0adfa0161d", 329 | "f4bf736b-ef05-4a25-9feb-e0edf57845bc", 330 | "2675a232-f4e8-4679-a7fa-da0a34ce88ee", 331 | "c660e12f-f90a-48b4-8ad3-d3d990a4baf7", 332 | "3afab531-6248-4ae6-b599-8a94593df600", 333 | "84ec66ab-9f97-4b8f-a173-9cf7e443ff87", 334 | "985af912-f49f-4b01-beac-bd1dbb910907", 335 | "ffde2b40-1045-41d1-b304-776e4f110136", 336 | "aeb4e823-1c8a-4b59-b1b6-1c65c04d9d28", 337 | "6cee99e4-8a69-45c8-a62c-5b1710b24f7f", 338 | "af7497ef-64d7-4ffc-8f37-333f6f6b75ff", 339 | "6556cea6-4f17-4997-a4a7-19b3ba93cf29", 340 | "f47015a5-64e7-4460-8cad-d4352f1344b3", 341 | "14b10af4-4c50-49a8-a0d9-8050e59b5b6d", 342 | "52e5e249-c341-4787-b65f-7cbbf3c4e0a6", 343 | "ecff460f-7ce0-4747-84d7-14319a371736", 344 | "50503e06-136d-445b-babe-aeb430973393", 345 | "bc507959-88ee-440a-b487-094f13fc8d22", 346 | "ffca1f1a-6dbd-4474-9e1c-0808721431b9", 347 | "0d91d34a-0715-4ca5-bf75-e521b3001a07", 348 | "1610c6ab-dd9d-423f-966f-8afe066cda07", 349 | "677b8204-8034-43fc-a134-57bcbc378614", 350 | "4ab20cb3-26dc-49a1-aef1-7850fb3731c9", 351 | "0244cce8-5f3b-4b38-a6ba-c39023d99a20", 352 | "65135764-f3e5-4257-99be-3b08b15daf52", 353 | "e52fd236-3718-491e-a513-b1c808bff74a", 354 | "7fe81c99-eaa0-4718-ae63-07dba018077d", 355 | "5ccc3e4d-4f5a-4ecd-b6ea-a5b4fa3439ec", 356 | "e9a667f7-9681-46ae-9f91-1019481961a5", 357 | "ca58603e-45fc-4bd1-b261-52faf62f441f", 358 | "0531802e-acf1-4be0-8794-a01fbd1ceb2e", 359 | "ff6761f0-97ab-4f0a-8114-07f3a61f7295", 360 | "9c6508f3-826c-4e78-b0f5-0617819bec50", 361 | "351394c8-4752-444b-9a93-e460903e840d", 362 | "0304ec44-987e-45d5-93ec-a69adfc23e9a", 363 | "b4438c9e-2e2b-4cec-a1fd-ff2eba68be73", 364 | "fdfe83f2-3a11-49db-80ed-15842ad6e79a", 365 | "a25557a3-7e91-469b-b36f-07cabc221973", 366 | "29ddaff4-ea07-42f7-8904-c8a25164bf5a", 367 | "b4dba488-d803-4194-806e-0c29a9f861ae", 368 | "9933b997-644d-4bb1-a2d7-dee511f05956", 369 | "a5464df7-12fc-474b-b81d-07e6cb04f519", 370 | "0c2f19c0-2776-45fb-9334-165d119d48b3", 371 | "11283bcc-57fe-412b-83b2-b7cfa2c1b45b", 372 | "2ad3fe4e-6f80-4027-bec7-3af1426fb05b", 373 | "0141c8e4-1e08-470e-85cf-c80707406f75", 374 | "64c2a78f-f7b4-4905-81d1-a7d9b3c92101", 375 | "8f878a28-d2b5-4af5-bf22-984d6ec7ebc1", 376 | "d2dafbeb-4c18-4c94-8918-049b3fda9bd0", 377 | "7594024c-b6b8-4df0-8652-eff17c689e55", 378 | "d3e8c53f-5432-4890-a231-d27414a8e9ee", 379 | "03a49d4c-6a91-41a4-b799-edf068a94e49", 380 | "ae420128-3e12-45ff-b36f-987f0aed2c3f", 381 | "163c8458-db48-4cc2-af40-15799cc3c8e6", 382 | "255e7e0e-451a-4f60-91f4-7d402af9fb2e", 383 | "5be39587-1092-4914-a751-4494c1696629", 384 | "34865eac-27ce-42d3-86bc-8b52c2be42c2", 385 | "8d85513e-afe6-4a2f-b24c-3e6acf87b941", 386 | "5126d793-8104-4a2d-bca7-a56c5619f633", 387 | "ff014cbf-ef57-4df8-8f3b-543492bd9f21", 388 | "4f8a43be-2b13-4438-a7e7-47fe7502166d", 389 | "0f6f267e-d86b-4837-864d-0e6e0cef2ac3", 390 | "660685b7-96c4-45c1-a8d4-63a35fef5dd1", 391 | "daef2a11-9813-42d9-a18b-6b9136e9b2f4", 392 | "d5264332-0133-4fe1-a337-c75553f7a40e", 393 | "027b1664-10a5-4d8e-8c74-ef0d208d31f1", 394 | "d1ee98b0-729c-45e7-87c0-63df0a11384f", 395 | "1c4e4616-9dbe-4986-9fb8-75514ded5bab", 396 | "3b142580-4dcd-4dfd-a6ea-2af6713111d7", 397 | "9a59e32a-0b69-4d0e-af1c-3cb7fede2bba", 398 | "35f724ab-ae47-4f66-964e-66fd4cf339d7", 399 | "b13cae6d-8675-4598-8167-e3f741420760", 400 | "22264180-fb1a-4649-93d8-07b2e613cae9", 401 | "e6368405-9a6b-41ed-9169-4dd414b712e0", 402 | "ed2a54b2-0752-47f5-a3d0-d513a13873a9", 403 | "1a55e31a-2d1b-4890-b357-0a0014739f6f", 404 | "07b84dce-dd41-462f-b3f1-45101cc986e1", 405 | "ffd714a9-e589-4454-9f2e-367c04dfe997", 406 | "da660a75-63a1-41c2-b810-0a6731306c17", 407 | "3fe0b160-2b4f-49a5-aed3-3b53c1c566bc", 408 | "3d2636ab-a5e4-44a8-8d75-5c3a542c32fb", 409 | "07cb8168-74ba-45f4-8504-c7312df16787", 410 | "eb13d21d-c19b-4b0f-b968-70bb6f307916", 411 | "087a14e6-8fcb-4790-bd46-e36e7cb20981", 412 | "d5c0c5bb-7750-417f-aa51-1d16d7a75648", 413 | "c0476d15-ca8e-4740-bdee-5e03de72a4e4", 414 | "c4b791cc-0354-480b-a788-1bb3139117a0", 415 | "0ca1d059-8425-48ad-980d-b5c781bdf9d6", 416 | "1777f381-4fde-4ffe-94ff-6270a6fb37de", 417 | "4a641f48-d43f-4eb5-a5ed-00456ffe83c6", 418 | "12b61b20-10a3-481d-92dc-79ca90a290a3", 419 | "1e65bf92-e094-46de-ac8c-a0f09428e9f8", 420 | "e12c5c97-0f51-496f-9900-78f2b51801af", 421 | "763dea68-6295-431d-9a12-9d7618c8c830", 422 | "bb8e512a-bbd6-462a-b09f-563efe556ca8", 423 | "7ac81f11-5bd0-4fa3-9f83-b812c9af9628", 424 | "1170bb12-ec17-401b-a94c-1a18508e7193", 425 | "1f7e406e-81c5-4629-ad62-a2f7f4803de6", 426 | "8483cfe0-99e8-4b49-ba24-39e6c442e19a", 427 | "f602cb6d-78d6-4949-be40-cad9539e2aa6", 428 | "96eb4f04-9d23-4c6c-b7f6-ce2ad202ad55", 429 | "6f812dc4-9039-45e2-935b-12867672a087", 430 | "2826b797-99f6-4f40-8a57-a8f4bb4353e7", 431 | "e9670bd0-3bdb-4064-8cbe-ff9c3d7b1426", 432 | "812d6b3f-c7c0-4da0-82bc-38530154e45b", 433 | "8f25d179-10e8-4b3b-8cb2-928b231648dd", 434 | "23330465-65ee-4ad3-8327-4ff8a1b3e533", 435 | "27bcb3f6-7db0-4efb-bd65-f4516a100010", 436 | "ad38fe28-6d58-4609-80f1-c2136be864df", 437 | "373555f4-f774-40ec-bba3-86bd2931b6f9", 438 | "fc97abce-3077-4e31-ae06-bd4038508ef7", 439 | "5239ee78-fb76-407e-837f-26b3c88d73bf", 440 | "8f9e96f8-a503-4603-aab6-c9e34eb866ca", 441 | "284b26cc-5962-4422-a2df-f10f048457a8", 442 | "bb156fee-2e96-43c1-bb39-c0d82ea4041c", 443 | "169d8db0-cf1d-467d-b60d-16ee9eceeb6f", 444 | "4cf097e7-25ac-4604-96c3-19c690d7f225", 445 | "efc0ead5-6aa3-46d8-8870-fc531f94ba2e", 446 | "54f1a03a-5e7a-41a9-a8eb-6995c6f71b33", 447 | "951ec42a-0d08-47e2-88e9-9f8906f5dad6", 448 | "f38cb576-4066-4ffd-b88e-d3c33ac35405", 449 | "05018ce5-5d79-4d51-8cdc-78e19db44792", 450 | "32612149-cf91-4a5b-8096-f7eb7b1135e7", 451 | "77be3e35-4865-4263-97a4-97f1ec8b4e0f", 452 | "c6da213d-a936-4e9b-8b41-e453dfc85d8b", 453 | "e7292277-d0ac-4b0a-a2d1-b9a7b6470e6d", 454 | "79184ffe-07e6-4426-97e5-0cf80bc06dfa", 455 | "d069223c-f697-4a22-b398-b57da79220ff", 456 | "bd652b5c-1cad-48a1-b663-e1b6e70ecbf8", 457 | "519b30a7-c0f2-4fa5-b571-1a7a3cdfc0cf", 458 | "696f3492-a32c-42d7-83f4-90e8de3997a8", 459 | "74ccdebd-3507-4ea1-a413-6c1cb9fec200", 460 | "e242f224-1022-4673-9b1c-e4dcd773f5bd", 461 | "5e653fe1-9dcb-40e8-b2fd-b215979822ff", 462 | "1d0d23e9-1ccb-4db2-9990-95fde63deb31", 463 | "c66aad40-1dee-43c2-810b-77532250bfc8", 464 | "50358178-f710-47e0-ab09-8d80a6ddd066", 465 | "f9de4e57-d710-4a20-9c40-bd1bcd059f4c", 466 | "476cb480-f2dc-4bba-99c9-db568c9cb023", 467 | "65630746-1420-48fe-9c07-59f842fd5484", 468 | "45a4f1ad-f19e-4c7c-af7d-0171cef79d3b", 469 | "a60a1c05-d48a-429f-b769-4a6a99a7ddc6", 470 | "81a87d43-8eef-4228-a3d2-af12ea4f324e", 471 | "102a271c-3be4-4245-a2f3-69920af963ed", 472 | "f47ca2e6-433b-49f4-97dc-b12f298cc14b", 473 | "9288f986-4114-4927-a23c-b1d9504d3900", 474 | "2e81266a-2296-44bd-ae8a-8b591a8de70d", 475 | "43958cc3-6de4-4d60-a71d-de1b937f26f0", 476 | "c236dab8-ec32-4ee6-87f4-661f496ba785", 477 | "060b001a-55d4-462a-8715-07dc769addc4", 478 | "8e307568-d062-4c5c-957c-b4049c46f809", 479 | "fd5ba869-7761-4815-9dd0-9ad3e339a424", 480 | "36631672-253e-4c77-9910-7200a452c276", 481 | "65ab8e1a-656b-422f-a80c-cbb5a7a5a145", 482 | "926efa69-14b3-427c-a2ae-7a95d2759656", 483 | "b6a8bc08-83e7-460f-81d0-ef7beb8ff614", 484 | "0d307cd1-c764-40fc-a0c2-647a6b226812", 485 | "8d88a619-c844-4424-8710-51d8a42d6547", 486 | "3dd07a59-91e4-4052-8608-1b7986ecf1a8", 487 | "64e38933-5e81-4708-b3ce-766ed3ccd6f1", 488 | "c151e16e-133a-4621-b10e-78471d828a18", 489 | "6b5545f6-811e-47ea-bbed-3f8afc505453", 490 | "0a1e55a0-f6a0-47cc-8ce6-331c2bae82bd", 491 | "44907432-b4de-4527-ae40-658528bd5525", 492 | "86304b2c-aff8-4c7b-a868-10cdde25a343", 493 | "4dd252c2-d6e6-4ca2-9db6-ec3a73bc7980", 494 | "704148fe-1ba6-4256-b15d-74f52e385a04", 495 | "66a50f77-9a39-46bf-9dde-1f171d5729e0", 496 | "0d39afd7-f005-46d5-84d3-80f364e17b67", 497 | "53569595-f4fb-4e72-8e4a-f04ead4fd8e0", 498 | "531a3ea9-9633-4fa8-b90e-6f3faec1d7b2", 499 | "191cfc73-2f34-4083-84a1-3b5596061800", 500 | "6f814307-7428-4bca-aeb5-5aa2b73395d3", 501 | "c9d70a0e-c1a4-4660-ac5b-94dd5603b0fa" 502 | ], 503 | "includeGroups": [], 504 | "excludeGroups": [], 505 | "includeRoles": [], 506 | "excludeRoles": [] 507 | }, 508 | "locations": { 509 | "includeLocations": [ 510 | "All" 511 | ], 512 | "excludeLocations": [] 513 | } 514 | }, 515 | "grantControls": { 516 | "operator": "OR", 517 | "builtInControls": [ 518 | "mfa" 519 | ], 520 | "customAuthenticationFactors": [], 521 | "termsOfUse": [] 522 | } 523 | }, 524 | { 525 | "id": "d945b1a2-2d3b-4b24-9d79-3533b1f73805", 526 | "displayName": "close-gaps", 527 | "createdDateTime": "2022-09-27T06:07:24.6241726Z", 528 | "modifiedDateTime": "2022-09-27T06:21:33.6189309Z", 529 | "state": "enabled", 530 | "sessionControls": null, 531 | "conditions": { 532 | "userRiskLevels": [], 533 | "signInRiskLevels": [], 534 | "clientAppTypes": [ 535 | "browser", 536 | "mobileAppsAndDesktopClients", 537 | "other" 538 | ], 539 | "servicePrincipalRiskLevels": [], 540 | "platforms": null, 541 | "locations": null, 542 | "devices": null, 543 | "clientApplications": null, 544 | "applications": { 545 | "includeApplications": [ 546 | "All" 547 | ], 548 | "excludeApplications": [ 549 | "88cc92be-d474-4d95-a57d-7b3ef701f510" 550 | ], 551 | "includeUserActions": [], 552 | "includeAuthenticationContextClassReferences": [] 553 | }, 554 | "users": { 555 | "includeUsers": [ 556 | "All" 557 | ], 558 | "excludeUsers": [ 559 | "259fcf40-ff7c-4625-9b78-cd11793f161f" 560 | ], 561 | "includeGroups": [], 562 | "excludeGroups": [], 563 | "includeRoles": [], 564 | "excludeRoles": [] 565 | } 566 | }, 567 | "grantControls": { 568 | "operator": "OR", 569 | "builtInControls": [ 570 | "mfa" 571 | ], 572 | "customAuthenticationFactors": [], 573 | "termsOfUse": [] 574 | } 575 | } 576 | ] -------------------------------------------------------------------------------- /demodata/lotOfGroups.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "4c096b25-2cbb-4a2a-a2e9-2cef12674adc", 4 | "displayName": "adminGrant", 5 | "createdDateTime": "2022-09-12T06:33:26.4671228Z", 6 | "modifiedDateTime": "2022-09-12T07:01:46.249428Z", 7 | "state": "enabled", 8 | "sessionControls": null, 9 | "conditions": { 10 | "userRiskLevels": [], 11 | "signInRiskLevels": [], 12 | "clientAppTypes": [ 13 | "all" 14 | ], 15 | "servicePrincipalRiskLevels": [], 16 | "platforms": null, 17 | "locations": null, 18 | "devices": null, 19 | "clientApplications": null, 20 | "applications": { 21 | "includeApplications": [ 22 | "All" 23 | ], 24 | "excludeApplications": [], 25 | "includeUserActions": [], 26 | "includeAuthenticationContextClassReferences": [] 27 | }, 28 | "users": { 29 | "includeUsers": [], 30 | "excludeUsers": [], 31 | "includeGroups": [], 32 | "excludeGroups": [], 33 | "includeRoles": [ 34 | "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3" 35 | ], 36 | "excludeRoles": [] 37 | } 38 | }, 39 | "grantControls": { 40 | "operator": "OR", 41 | "builtInControls": [ 42 | "mfa" 43 | ], 44 | "customAuthenticationFactors": [], 45 | "termsOfUse": [] 46 | } 47 | }, 48 | { 49 | "id": "f4898dba-ffd0-480d-862d-79ac3ccc05e2", 50 | "displayName": "baseline", 51 | "createdDateTime": "2022-09-12T06:58:25.6356601Z", 52 | "modifiedDateTime": "2022-09-12T10:56:48.5611298Z", 53 | "state": "enabled", 54 | "sessionControls": null, 55 | "conditions": { 56 | "userRiskLevels": [], 57 | "signInRiskLevels": [], 58 | "clientAppTypes": [ 59 | "all" 60 | ], 61 | "servicePrincipalRiskLevels": [], 62 | "platforms": null, 63 | "locations": null, 64 | "devices": null, 65 | "clientApplications": null, 66 | "applications": { 67 | "includeApplications": [ 68 | "All" 69 | ], 70 | "excludeApplications": [], 71 | "includeUserActions": [], 72 | "includeAuthenticationContextClassReferences": [] 73 | }, 74 | "users": { 75 | "includeUsers": [ 76 | "All" 77 | ], 78 | "excludeUsers": [ 79 | "259fcf40-ff7c-4625-9b78-cd11793f161f" 80 | ], 81 | "includeGroups": [], 82 | "excludeGroups": [ 83 | "6e41b62d-752b-42c6-9f34-d6ac90eddca3", 84 | "a7a34ae9-b8ce-4c9e-af7b-d32790734b18" 85 | ], 86 | "includeRoles": [], 87 | "excludeRoles": [ 88 | "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3" 89 | ] 90 | } 91 | }, 92 | "grantControls": { 93 | "operator": "OR", 94 | "builtInControls": [ 95 | "mfa" 96 | ], 97 | "customAuthenticationFactors": [], 98 | "termsOfUse": [] 99 | } 100 | } 101 | ] -------------------------------------------------------------------------------- /demodata/multipleGuestTypes.json: -------------------------------------------------------------------------------- 1 | [{"id":"ad21b9f5-1931-4c5b-8918-aa905bd1da7c","displayName":"blockAll","createdDateTime":"2022-10-13T06:07:41.2871117Z","modifiedDateTime":"2022-10-17T09:42:36.7965649Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"locations":null,"times":null,"deviceStates":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":["259fcf40-ff7c-4625-9b78-cd11793f161f","GuestsOrExternalUsers"],"includeGroups":[],"excludeGroups":["a881d286-2b5c-417b-8d7e-c48b516aef83"],"includeRoles":[],"excludeRoles":["9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3"],"includeGuestsOrExternalUsers":null,"excludeGuestsOrExternalUsers":null}},"grantControls":{"operator":"OR","builtInControls":["block"],"customAuthenticationFactors":[],"termsOfUse":[],"authenticationStrength@odata.context":"https://graph.microsoft.com/beta/$metadata#identity/conditionalAccess/policies('ad21b9f5-1931-4c5b-8918-aa905bd1da7c')/grantControls/authenticationStrength/$entity","authenticationStrength":null}},{"id":"59d6b116-48a7-4086-afb1-f914bb40e01a","displayName":"handle marketing","createdDateTime":"2022-10-13T06:08:51.6474503Z","modifiedDateTime":"2022-10-24T03:13:51.6672379Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"locations":null,"times":null,"deviceStates":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["82437bb1-7a18-4701-b0d6-4cd0eaf3bb80"],"excludeUsers":[],"includeGroups":["a881d286-2b5c-417b-8d7e-c48b516aef83","0f30c490-7522-4ed6-b5aa-ef1308789093"],"excludeGroups":[],"includeRoles":[],"excludeRoles":[],"includeGuestsOrExternalUsers":null,"excludeGuestsOrExternalUsers":null},"devices":{"includeDeviceStates":[],"excludeDeviceStates":[],"includeDevices":[],"excludeDevices":[],"deviceFilter":{"mode":"exclude","rule":"device.trustType -eq \"ServerAD\""}}},"grantControls":{"operator":"OR","builtInControls":["block"],"customAuthenticationFactors":[],"termsOfUse":[],"authenticationStrength@odata.context":"https://graph.microsoft.com/beta/$metadata#identity/conditionalAccess/policies('59d6b116-48a7-4086-afb1-f914bb40e01a')/grantControls/authenticationStrength/$entity","authenticationStrength":null}},{"id":"278f670a-4bac-46ba-bd27-b5ab29a25bc6","displayName":"SDa","createdDateTime":"2022-10-21T12:11:08.650161Z","modifiedDateTime":"2022-10-24T03:18:08.8401078Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"locations":null,"times":null,"deviceStates":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":["88cc92be-d474-4d95-a57d-7b3ef701f510"],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":[],"excludeUsers":[],"includeGroups":["0f30c490-7522-4ed6-b5aa-ef1308789093"],"excludeGroups":[],"includeRoles":["9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3"],"excludeRoles":[],"excludeGuestsOrExternalUsers":null,"includeGuestsOrExternalUsers":{"guestOrExternalUserTypes":"internalGuest,b2bCollaborationGuest,b2bCollaborationMember,b2bDirectConnectUser,otherExternalUser,serviceProvider","externalTenants":{"@odata.type":"#microsoft.graph.conditionalAccessAllExternalTenants","membershipKind":"all"}}}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[],"authenticationStrength@odata.context":"https://graph.microsoft.com/beta/$metadata#identity/conditionalAccess/policies('278f670a-4bac-46ba-bd27-b5ab29a25bc6')/grantControls/authenticationStrength/$entity","authenticationStrength":null}},{"id":"2fe377c5-7185-4828-8168-a6c1d82c0a9b","displayName":"sda2","createdDateTime":"2022-10-21T12:16:28.5005879Z","modifiedDateTime":"2022-10-24T03:20:46.3911885Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"times":null,"deviceStates":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["88cc92be-d474-4d95-a57d-7b3ef701f510"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["82437bb1-7a18-4701-b0d6-4cd0eaf3bb80"],"excludeUsers":[],"includeGroups":["0f30c490-7522-4ed6-b5aa-ef1308789093"],"excludeGroups":[],"includeRoles":["9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3"],"excludeRoles":[],"excludeGuestsOrExternalUsers":null,"includeGuestsOrExternalUsers":{"guestOrExternalUserTypes":"internalGuest,b2bCollaborationGuest,b2bCollaborationMember,b2bDirectConnectUser,otherExternalUser,serviceProvider","externalTenants":{"@odata.type":"#microsoft.graph.conditionalAccessAllExternalTenants","membershipKind":"all"}}},"platforms":{"includePlatforms":["android","iOS","windows","windowsPhone","macOS","linux"],"excludePlatforms":[]},"locations":{"includeLocations":["All"],"excludeLocations":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[],"authenticationStrength@odata.context":"https://graph.microsoft.com/beta/$metadata#identity/conditionalAccess/policies('2fe377c5-7185-4828-8168-a6c1d82c0a9b')/grantControls/authenticationStrength/$entity","authenticationStrength":null}}] -------------------------------------------------------------------------------- /demodata/namedLocations.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "@odata.type": "#microsoft.graph.ipNamedLocation", 3 | "id": "0935449a-60af-42b0-a4aa-94ff2a203d90", 4 | "displayName": "87.92.160.34", 5 | "modifiedDateTime": "2022-08-15T10:26:57.0242157Z", 6 | "createdDateTime": "2022-05-10T04:11:15.671631Z", 7 | "isTrusted": true, 8 | "ipRanges": [{ 9 | "@odata.type": "#microsoft.graph.iPv4CidrRange", 10 | "cidrAddress": "87.92.49.122/32" 11 | }] 12 | }, { 13 | "@odata.type": "#microsoft.graph.countryNamedLocation", 14 | "id": "10a25087-9bf6-479b-a2a1-37e814310c90", 15 | "displayName": "finland", 16 | "modifiedDateTime": "2022-08-15T10:27:12.3296971Z", 17 | "createdDateTime": "2022-08-15T10:27:12.3296971Z", 18 | "countriesAndRegions": ["FI"], 19 | "includeUnknownCountriesAndRegions": false, 20 | "countryLookupMethod": "clientIpAddress" 21 | }, { 22 | "@odata.type": "#microsoft.graph.countryNamedLocation", 23 | "id": "1786d979-9a7d-49dc-aa76-a5f4358976bd", 24 | "displayName": "switzerland", 25 | "modifiedDateTime": "2022-08-23T16:26:55.6296287Z", 26 | "createdDateTime": "2022-08-23T16:26:55.6296287Z", 27 | "countriesAndRegions": ["CH"], 28 | "includeUnknownCountriesAndRegions": false, 29 | "countryLookupMethod": "clientIpAddress" 30 | }] -------------------------------------------------------------------------------- /demodata/policies.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": "a0ff5ad3-8e79-442d-a999-7c20d0e38ddd", 3 | "displayName": "Handle macOs", 4 | "createdDateTime": "2022-09-16T06:21:16.6103941Z", 5 | "modifiedDateTime": "2022-09-16T06:49:46.0163051Z", 6 | "state": "enabled", 7 | "sessionControls": null, 8 | "conditions": { 9 | "userRiskLevels": [], 10 | "signInRiskLevels": [], 11 | "clientAppTypes": ["mobileAppsAndDesktopClients"], 12 | "servicePrincipalRiskLevels": [], 13 | "locations": null, 14 | "devices": null, 15 | "clientApplications": null, 16 | "applications": { 17 | "includeApplications": ["All"], 18 | "excludeApplications": [], 19 | "includeUserActions": [], 20 | "includeAuthenticationContextClassReferences": [] 21 | }, 22 | "users": { 23 | "includeUsers": ["All"], 24 | "excludeUsers": ["259fcf40-ff7c-4625-9b78-cd11793f161f"], 25 | "includeGroups": [], 26 | "excludeGroups": [], 27 | "includeRoles": [], 28 | "excludeRoles": [] 29 | }, 30 | "platforms": { 31 | "includePlatforms": ["macOS"], 32 | "excludePlatforms": [] 33 | } 34 | }, 35 | "grantControls": { 36 | "operator": "OR", 37 | "builtInControls": ["mfa"], 38 | "customAuthenticationFactors": [], 39 | "termsOfUse": [] 40 | } 41 | }, { 42 | "id": "f804a06c-fb50-4aa0-a93b-f1371072d2b9", 43 | "displayName": "baseline", 44 | "createdDateTime": "2022-09-16T06:21:13.8045798Z", 45 | "modifiedDateTime": null, 46 | "state": "enabled", 47 | "sessionControls": null, 48 | "conditions": { 49 | "userRiskLevels": [], 50 | "signInRiskLevels": [], 51 | "clientAppTypes": ["all"], 52 | "servicePrincipalRiskLevels": [], 53 | "platforms": null, 54 | "devices": null, 55 | "clientApplications": null, 56 | "applications": { 57 | "includeApplications": ["All"], 58 | "excludeApplications": [], 59 | "includeUserActions": [], 60 | "includeAuthenticationContextClassReferences": [] 61 | }, 62 | "users": { 63 | "includeUsers": ["All"], 64 | "excludeUsers": ["259fcf40-ff7c-4625-9b78-cd11793f161f"], 65 | "includeGroups": [], 66 | "excludeGroups": [], 67 | "includeRoles": [], 68 | "excludeRoles": [] 69 | }, 70 | "locations": { 71 | "includeLocations": ["All"], 72 | "excludeLocations": ["10a25087-9bf6-479b-a2a1-37e814310c90"] 73 | } 74 | }, 75 | "grantControls": { 76 | "operator": "OR", 77 | "builtInControls": ["mfa"], 78 | "customAuthenticationFactors": [], 79 | "termsOfUse": [] 80 | } 81 | }, { 82 | "id": "ba0047a1-4067-486f-b3ad-28838df44832", 83 | "displayName": "Finland", 84 | "createdDateTime": "2022-09-16T06:21:15.2906255Z", 85 | "modifiedDateTime": null, 86 | "state": "enabled", 87 | "sessionControls": null, 88 | "conditions": { 89 | "userRiskLevels": [], 90 | "signInRiskLevels": [], 91 | "clientAppTypes": ["all"], 92 | "servicePrincipalRiskLevels": [], 93 | "devices": null, 94 | "clientApplications": null, 95 | "applications": { 96 | "includeApplications": ["All"], 97 | "excludeApplications": [], 98 | "includeUserActions": [], 99 | "includeAuthenticationContextClassReferences": [] 100 | }, 101 | "users": { 102 | "includeUsers": ["All"], 103 | "excludeUsers": ["259fcf40-ff7c-4625-9b78-cd11793f161f"], 104 | "includeGroups": [], 105 | "excludeGroups": [], 106 | "includeRoles": [], 107 | "excludeRoles": [] 108 | }, 109 | "platforms": { 110 | "includePlatforms": ["all"], 111 | "excludePlatforms": ["macOS"] 112 | }, 113 | "locations": { 114 | "includeLocations": ["10a25087-9bf6-479b-a2a1-37e814310c90"], 115 | "excludeLocations": [] 116 | } 117 | }, 118 | "grantControls": { 119 | "operator": "OR", 120 | "builtInControls": ["mfa"], 121 | "customAuthenticationFactors": [], 122 | "termsOfUse": [] 123 | } 124 | }] -------------------------------------------------------------------------------- /demodata/policies2.json: -------------------------------------------------------------------------------- 1 | [{"id":"2c84c814-2aef-4bcf-ba7a-55fd83e3176b","displayName":"baseline","createdDateTime":"2022-09-10T04:39:20.8168738Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"fe067e9b-b8ca-4da0-84d9-1a49e24bd6c2","displayName":"Finland","createdDateTime":"2022-09-10T04:40:18.4486769Z","modifiedDateTime":"2022-09-11T05:41:37.1328391Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"platforms":{"includePlatforms":["android","iOS","macOS","linux"],"excludePlatforms":[]},"locations":{"includeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"],"excludeLocations":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"ac2e1c9b-f288-4fd9-9a9d-297e470bf2d2","displayName":"WindowsPhone All","createdDateTime":"2022-09-10T04:45:28.0018771Z","modifiedDateTime":"2022-09-10T04:46:58.3764282Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":["GuestsOrExternalUsers"],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"platforms":{"includePlatforms":["windowsPhone"],"excludePlatforms":[]},"locations":{"includeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"],"excludeLocations":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}}] -------------------------------------------------------------------------------- /demodata/testMapping.json: -------------------------------------------------------------------------------- 1 | [{"id":"530f651b-03aa-4d3a-badf-ad80dfb1a654","displayName":"CA004: Require multifactor authentication for all users","createdDateTime":"2022-10-05T07:01:56.4217715Z","modifiedDateTime":"2022-10-06T07:18:05.3838032Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":["88cc92be-d474-4d95-a57d-7b3ef701f510","7b7531ad-5926-4f2d-8a1d-38495ad33e17","ea890292-c8c8-4433-b5ea-b09d0668e1a6","499b84ac-1321-427f-aa17-267ca6975798","803ee9ca-3f7f-4824-bd6e-0b99d720c35c","00000007-0000-0000-c000-000000000000","ae1ece49-cc61-4d08-86c2-b0246abc3f6a","00000012-0000-0000-c000-000000000000","05a65629-4c1b-48c1-a78b-804c4abdd4af","8f41dc7c-542c-4bdd-8eb3-e60543f607ca","c9a559d2-7aab-4f13-a6ed-e7e9c52aec87","40775b29-2688-46b6-a3b5-b256bd04df9f","Office365"],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"68b222ff-7e44-4209-8b69-3981bc6390ab","displayName":"Handle Finland ","createdDateTime":"2022-10-05T07:03:10.9770467Z","modifiedDateTime":"2022-10-06T07:14:58.6679862Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":["175a4a60-dae2-4871-9a18-eb497a8868ec","d02fce95-d752-4ac8-9ca9-8c15cba1a26c","d0846dbe-e80c-433d-957c-cbcb9f4cbaaa","433f4482-8aab-4ceb-bc13-2ddfb8941986","d8bfa72d-e26f-43cc-a597-bc0a21b1575f","2336d1ce-4c80-4305-b59a-901794fd91d6","0a69ce8e-d569-44c2-905f-9b36d9674ae9","e42a481b-be70-4476-b189-02dd87f9a686"],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"],"excludeLocations":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"7cfabe25-df1a-4b23-bdbd-45cf5b5f4474","displayName":"handle certain groups","createdDateTime":"2022-10-06T07:15:39.6080279Z","modifiedDateTime":"2022-10-06T07:21:24.0398937Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"locations":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["GuestsOrExternalUsers"],"excludeUsers":[],"includeGroups":["85d7d239-94ea-40fb-9826-6bfa47e90398","ecef3b48-cf59-453c-9db6-1da48690f733","4c9ad188-5620-4523-b761-6293802db8a6","1b527587-6d25-4b1c-8f4e-a0030d4a5b70","c4b75406-a6be-4e13-b469-76979a066b4a","d3f78026-5613-4889-8fd8-41649d3af6e5","153c1133-1dc0-43a7-800e-d5be27e45a1a","36b94df7-70b6-4054-a528-79f067077757","d3ae1596-4969-471d-a1e6-7728585da3f8","a18e69cb-318b-4da2-85e4-6e0ef3f244e5","7aa3f18f-0dca-4179-a96d-eb4f52f1fb20"],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"06b5109a-07c8-46af-96f3-7ac9b04c036b","displayName":"mgmtApiReader","createdDateTime":"2022-10-06T07:24:00.7457258Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["ae1ece49-cc61-4d08-86c2-b0246abc3f6a"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"5ebd3fed-5fd4-4c4e-bcb2-8204ec00aeb5","displayName":"sda3","createdDateTime":"2022-10-06T07:24:01.8035875Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["8d062ed1-bb17-465c-b02f-7877cae54483"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"56020d52-6489-487a-ac54-ffa6c3712650","displayName":"api-1313","createdDateTime":"2022-10-06T07:24:02.9494258Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["88cc92be-d474-4d95-a57d-7b3ef701f510"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"2fa0f38a-559e-4c01-acec-fb604ac833ad","displayName":"spa_cif","createdDateTime":"2022-10-06T07:24:03.9027745Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["9e78a010-6344-4862-84eb-822360016f6a"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"b62d95a7-04e6-44b6-ac49-b4e98740c3a8","displayName":"api-14736","createdDateTime":"2022-10-06T07:24:07.6150565Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["881b5d33-671a-43b9-8fd1-ba1e446f607f"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"98c5a4bb-dc97-4270-b425-64afbcb1c830","displayName":"baseline","createdDateTime":"2022-10-06T07:24:08.5390142Z","modifiedDateTime":"2022-10-10T09:16:45.2994298Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":["ae1ece49-cc61-4d08-86c2-b0246abc3f6a","8d062ed1-bb17-465c-b02f-7877cae54483","88cc92be-d474-4d95-a57d-7b3ef701f510","9e78a010-6344-4862-84eb-822360016f6a"],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":[],"excludeUsers":["d8bfa72d-e26f-43cc-a597-bc0a21b1575f","433f4482-8aab-4ceb-bc13-2ddfb8941986","d0846dbe-e80c-433d-957c-cbcb9f4cbaaa","3e845433-ec5f-4209-909f-00b118c133bd","d02fce95-d752-4ac8-9ca9-8c15cba1a26c","175a4a60-dae2-4871-9a18-eb497a8868ec","4645297d-0032-44b3-976c-c9e69d99f3c0","e75ef692-198b-4218-8d06-5ac5ca225d35","f909c7b7-7e03-42d3-84a9-b0189322eb99","71207ae7-f02a-4079-9f22-fb204432c7d6","ed648df1-6ab3-4a23-89cd-e85e08097c82","730a89e4-6934-4f3b-b75e-0b28cb943995","f426953f-773c-4ea3-a582-8f35f2e694a3","fea0612e-cfde-4b32-bdd4-e3273b969c0a","2617eadf-9b50-471a-8d3b-899e89f99cd0","df7e1cef-69d0-46fd-afaa-ed40b6360084","82e8600a-a38b-48c7-a4d1-07dfd8491f96","6d8499d0-340c-4e9c-848a-a146174f7c63","711d918e-c2d0-4e6b-9f72-de26327d85ae","ac9944c2-aee5-406b-a168-b6de268b018e","0fb0e2ed-e7d0-4edf-94c4-0375b185f73c","ac7a0055-54d6-477e-9145-9ac42d83b423","e42a481b-be70-4476-b189-02dd87f9a686","0a69ce8e-d569-44c2-905f-9b36d9674ae9","2336d1ce-4c80-4305-b59a-901794fd91d6","fb7c8be4-53a4-4093-8d9a-9edd3020bdd0","01ae6d6a-4b8d-4717-9db5-cb0adfa0161d","f4bf736b-ef05-4a25-9feb-e0edf57845bc","2675a232-f4e8-4679-a7fa-da0a34ce88ee","c660e12f-f90a-48b4-8ad3-d3d990a4baf7","3afab531-6248-4ae6-b599-8a94593df600","84ec66ab-9f97-4b8f-a173-9cf7e443ff87","985af912-f49f-4b01-beac-bd1dbb910907","ffde2b40-1045-41d1-b304-776e4f110136","aeb4e823-1c8a-4b59-b1b6-1c65c04d9d28","6cee99e4-8a69-45c8-a62c-5b1710b24f7f","af7497ef-64d7-4ffc-8f37-333f6f6b75ff","6556cea6-4f17-4997-a4a7-19b3ba93cf29","f47015a5-64e7-4460-8cad-d4352f1344b3","14b10af4-4c50-49a8-a0d9-8050e59b5b6d","52e5e249-c341-4787-b65f-7cbbf3c4e0a6","ecff460f-7ce0-4747-84d7-14319a371736","50503e06-136d-445b-babe-aeb430973393","bc507959-88ee-440a-b487-094f13fc8d22","ffca1f1a-6dbd-4474-9e1c-0808721431b9","0d91d34a-0715-4ca5-bf75-e521b3001a07","1610c6ab-dd9d-423f-966f-8afe066cda07","677b8204-8034-43fc-a134-57bcbc378614","4ab20cb3-26dc-49a1-aef1-7850fb3731c9","0244cce8-5f3b-4b38-a6ba-c39023d99a20","65135764-f3e5-4257-99be-3b08b15daf52","e52fd236-3718-491e-a513-b1c808bff74a","7fe81c99-eaa0-4718-ae63-07dba018077d","5ccc3e4d-4f5a-4ecd-b6ea-a5b4fa3439ec","e9a667f7-9681-46ae-9f91-1019481961a5","ca58603e-45fc-4bd1-b261-52faf62f441f","0531802e-acf1-4be0-8794-a01fbd1ceb2e","ff6761f0-97ab-4f0a-8114-07f3a61f7295","9c6508f3-826c-4e78-b0f5-0617819bec50","351394c8-4752-444b-9a93-e460903e840d","0304ec44-987e-45d5-93ec-a69adfc23e9a","b4438c9e-2e2b-4cec-a1fd-ff2eba68be73","fdfe83f2-3a11-49db-80ed-15842ad6e79a","a25557a3-7e91-469b-b36f-07cabc221973","29ddaff4-ea07-42f7-8904-c8a25164bf5a","b4dba488-d803-4194-806e-0c29a9f861ae","9933b997-644d-4bb1-a2d7-dee511f05956","a5464df7-12fc-474b-b81d-07e6cb04f519","0c2f19c0-2776-45fb-9334-165d119d48b3","11283bcc-57fe-412b-83b2-b7cfa2c1b45b","2ad3fe4e-6f80-4027-bec7-3af1426fb05b","0141c8e4-1e08-470e-85cf-c80707406f75","64c2a78f-f7b4-4905-81d1-a7d9b3c92101","8f878a28-d2b5-4af5-bf22-984d6ec7ebc1","d2dafbeb-4c18-4c94-8918-049b3fda9bd0","7594024c-b6b8-4df0-8652-eff17c689e55","d3e8c53f-5432-4890-a231-d27414a8e9ee","03a49d4c-6a91-41a4-b799-edf068a94e49","ae420128-3e12-45ff-b36f-987f0aed2c3f","163c8458-db48-4cc2-af40-15799cc3c8e6","255e7e0e-451a-4f60-91f4-7d402af9fb2e","5be39587-1092-4914-a751-4494c1696629","34865eac-27ce-42d3-86bc-8b52c2be42c2","8d85513e-afe6-4a2f-b24c-3e6acf87b941","5126d793-8104-4a2d-bca7-a56c5619f633","ff014cbf-ef57-4df8-8f3b-543492bd9f21","4f8a43be-2b13-4438-a7e7-47fe7502166d","0f6f267e-d86b-4837-864d-0e6e0cef2ac3","660685b7-96c4-45c1-a8d4-63a35fef5dd1","daef2a11-9813-42d9-a18b-6b9136e9b2f4","d5264332-0133-4fe1-a337-c75553f7a40e","027b1664-10a5-4d8e-8c74-ef0d208d31f1","d1ee98b0-729c-45e7-87c0-63df0a11384f","1c4e4616-9dbe-4986-9fb8-75514ded5bab","3b142580-4dcd-4dfd-a6ea-2af6713111d7","9a59e32a-0b69-4d0e-af1c-3cb7fede2bba","35f724ab-ae47-4f66-964e-66fd4cf339d7","b13cae6d-8675-4598-8167-e3f741420760","22264180-fb1a-4649-93d8-07b2e613cae9","e6368405-9a6b-41ed-9169-4dd414b712e0","ed2a54b2-0752-47f5-a3d0-d513a13873a9","1a55e31a-2d1b-4890-b357-0a0014739f6f","07b84dce-dd41-462f-b3f1-45101cc986e1","ffd714a9-e589-4454-9f2e-367c04dfe997","da660a75-63a1-41c2-b810-0a6731306c17","3fe0b160-2b4f-49a5-aed3-3b53c1c566bc","3d2636ab-a5e4-44a8-8d75-5c3a542c32fb","07cb8168-74ba-45f4-8504-c7312df16787","eb13d21d-c19b-4b0f-b968-70bb6f307916","087a14e6-8fcb-4790-bd46-e36e7cb20981","d5c0c5bb-7750-417f-aa51-1d16d7a75648","c0476d15-ca8e-4740-bdee-5e03de72a4e4","c4b791cc-0354-480b-a788-1bb3139117a0","0ca1d059-8425-48ad-980d-b5c781bdf9d6","1777f381-4fde-4ffe-94ff-6270a6fb37de","4a641f48-d43f-4eb5-a5ed-00456ffe83c6","12b61b20-10a3-481d-92dc-79ca90a290a3","1e65bf92-e094-46de-ac8c-a0f09428e9f8","e12c5c97-0f51-496f-9900-78f2b51801af","763dea68-6295-431d-9a12-9d7618c8c830","bb8e512a-bbd6-462a-b09f-563efe556ca8","7ac81f11-5bd0-4fa3-9f83-b812c9af9628","1170bb12-ec17-401b-a94c-1a18508e7193","1f7e406e-81c5-4629-ad62-a2f7f4803de6","8483cfe0-99e8-4b49-ba24-39e6c442e19a","f602cb6d-78d6-4949-be40-cad9539e2aa6","96eb4f04-9d23-4c6c-b7f6-ce2ad202ad55","6f812dc4-9039-45e2-935b-12867672a087","2826b797-99f6-4f40-8a57-a8f4bb4353e7","e9670bd0-3bdb-4064-8cbe-ff9c3d7b1426","812d6b3f-c7c0-4da0-82bc-38530154e45b","8f25d179-10e8-4b3b-8cb2-928b231648dd","23330465-65ee-4ad3-8327-4ff8a1b3e533","27bcb3f6-7db0-4efb-bd65-f4516a100010","ad38fe28-6d58-4609-80f1-c2136be864df","373555f4-f774-40ec-bba3-86bd2931b6f9","fc97abce-3077-4e31-ae06-bd4038508ef7","5239ee78-fb76-407e-837f-26b3c88d73bf","8f9e96f8-a503-4603-aab6-c9e34eb866ca","284b26cc-5962-4422-a2df-f10f048457a8","bb156fee-2e96-43c1-bb39-c0d82ea4041c","169d8db0-cf1d-467d-b60d-16ee9eceeb6f","4cf097e7-25ac-4604-96c3-19c690d7f225","efc0ead5-6aa3-46d8-8870-fc531f94ba2e","54f1a03a-5e7a-41a9-a8eb-6995c6f71b33","951ec42a-0d08-47e2-88e9-9f8906f5dad6","f38cb576-4066-4ffd-b88e-d3c33ac35405","05018ce5-5d79-4d51-8cdc-78e19db44792","32612149-cf91-4a5b-8096-f7eb7b1135e7","77be3e35-4865-4263-97a4-97f1ec8b4e0f","c6da213d-a936-4e9b-8b41-e453dfc85d8b","e7292277-d0ac-4b0a-a2d1-b9a7b6470e6d","79184ffe-07e6-4426-97e5-0cf80bc06dfa","d069223c-f697-4a22-b398-b57da79220ff","bd652b5c-1cad-48a1-b663-e1b6e70ecbf8","519b30a7-c0f2-4fa5-b571-1a7a3cdfc0cf","696f3492-a32c-42d7-83f4-90e8de3997a8","74ccdebd-3507-4ea1-a413-6c1cb9fec200","e242f224-1022-4673-9b1c-e4dcd773f5bd","5e653fe1-9dcb-40e8-b2fd-b215979822ff","1d0d23e9-1ccb-4db2-9990-95fde63deb31","c66aad40-1dee-43c2-810b-77532250bfc8","50358178-f710-47e0-ab09-8d80a6ddd066","f9de4e57-d710-4a20-9c40-bd1bcd059f4c","476cb480-f2dc-4bba-99c9-db568c9cb023","65630746-1420-48fe-9c07-59f842fd5484","45a4f1ad-f19e-4c7c-af7d-0171cef79d3b","a60a1c05-d48a-429f-b769-4a6a99a7ddc6","81a87d43-8eef-4228-a3d2-af12ea4f324e","102a271c-3be4-4245-a2f3-69920af963ed","f47ca2e6-433b-49f4-97dc-b12f298cc14b","9288f986-4114-4927-a23c-b1d9504d3900","2e81266a-2296-44bd-ae8a-8b591a8de70d","43958cc3-6de4-4d60-a71d-de1b937f26f0","c236dab8-ec32-4ee6-87f4-661f496ba785","060b001a-55d4-462a-8715-07dc769addc4","8e307568-d062-4c5c-957c-b4049c46f809","fd5ba869-7761-4815-9dd0-9ad3e339a424","36631672-253e-4c77-9910-7200a452c276","65ab8e1a-656b-422f-a80c-cbb5a7a5a145","926efa69-14b3-427c-a2ae-7a95d2759656","b6a8bc08-83e7-460f-81d0-ef7beb8ff614","0d307cd1-c764-40fc-a0c2-647a6b226812","8d88a619-c844-4424-8710-51d8a42d6547","3dd07a59-91e4-4052-8608-1b7986ecf1a8","64e38933-5e81-4708-b3ce-766ed3ccd6f1","c151e16e-133a-4621-b10e-78471d828a18","6b5545f6-811e-47ea-bbed-3f8afc505453","0a1e55a0-f6a0-47cc-8ce6-331c2bae82bd","44907432-b4de-4527-ae40-658528bd5525","86304b2c-aff8-4c7b-a868-10cdde25a343","4dd252c2-d6e6-4ca2-9db6-ec3a73bc7980","704148fe-1ba6-4256-b15d-74f52e385a04","66a50f77-9a39-46bf-9dde-1f171d5729e0","0d39afd7-f005-46d5-84d3-80f364e17b67","53569595-f4fb-4e72-8e4a-f04ead4fd8e0","531a3ea9-9633-4fa8-b90e-6f3faec1d7b2","191cfc73-2f34-4083-84a1-3b5596061800","6f814307-7428-4bca-aeb5-5aa2b73395d3","c9d70a0e-c1a4-4660-ac5b-94dd5603b0fa"],"includeGroups":["a74f4f85-7e30-4355-acef-f81fd5e051cf"],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}}] -------------------------------------------------------------------------------- /demodata/wLa.json: -------------------------------------------------------------------------------- 1 | [{"id":"34a4bcd0-8005-4ff8-a03f-903e145e1c33","displayName":"baseline","createdDateTime":"2022-09-15T07:12:35.167686Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":["259fcf40-ff7c-4625-9b78-cd11793f161f","GuestsOrExternalUsers"],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"c9a2702e-7d7a-47e5-a553-143e6fe18e61","displayName":"finski","createdDateTime":"2022-09-15T07:12:35.8667436Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":["259fcf40-ff7c-4625-9b78-cd11793f161f"],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"platforms":{"includePlatforms":["all"],"excludePlatforms":["macOS"]},"locations":{"includeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"],"excludeLocations":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"eafa68cb-b498-422d-b5bc-19f73281913d","displayName":"Handle macOs","createdDateTime":"2022-09-15T07:12:36.6304774Z","modifiedDateTime":"2022-09-15T09:10:41.0518951Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["browser","mobileAppsAndDesktopClients"],"servicePrincipalRiskLevels":[],"locations":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":["259fcf40-ff7c-4625-9b78-cd11793f161f"],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"platforms":{"includePlatforms":["macOS"],"excludePlatforms":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"330891e5-2ac8-4081-a835-4385e510da4c","displayName":"CA005: Require multifactor authentication for guest access","createdDateTime":"2022-09-15T07:12:37.3079829Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"locations":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":["88cc92be-d474-4d95-a57d-7b3ef701f510"],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["GuestsOrExternalUsers"],"excludeUsers":["259fcf40-ff7c-4625-9b78-cd11793f161f"],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"8a2d5f82-50ee-4699-afc6-66630f2afc22","displayName":"Handle Office 365","createdDateTime":"2022-09-15T07:12:38.2730818Z","modifiedDateTime":null,"state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["all"],"servicePrincipalRiskLevels":[],"platforms":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["Office365"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]},"locations":{"includeLocations":["All"],"excludeLocations":["10a25087-9bf6-479b-a2a1-37e814310c90"]}},"grantControls":{"operator":"OR","builtInControls":["compliantDevice"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"e6b8c79a-3787-4c2e-8f2f-668fb7ee4c17","displayName":"Guests handle rare API","createdDateTime":"2022-09-15T07:12:38.9762264Z","modifiedDateTime":"2022-09-15T09:09:37.7632168Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["mobileAppsAndDesktopClients"],"servicePrincipalRiskLevels":[],"platforms":null,"locations":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["88cc92be-d474-4d95-a57d-7b3ef701f510"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["GuestsOrExternalUsers"],"excludeUsers":[],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]}},"grantControls":{"operator":"OR","builtInControls":["mfa"],"customAuthenticationFactors":[],"termsOfUse":[]}},{"id":"9258e8f1-3061-4bc8-a4e7-547561569520","displayName":"CA003: Block legacy authentication","createdDateTime":"2022-09-16T01:53:46.5966508Z","modifiedDateTime":"2022-09-16T02:50:12.5603923Z","state":"enabled","sessionControls":null,"conditions":{"userRiskLevels":[],"signInRiskLevels":[],"clientAppTypes":["exchangeActiveSync","other"],"servicePrincipalRiskLevels":[],"platforms":null,"locations":null,"devices":null,"clientApplications":null,"applications":{"includeApplications":["All"],"excludeApplications":[],"includeUserActions":[],"includeAuthenticationContextClassReferences":[]},"users":{"includeUsers":["All"],"excludeUsers":["259fcf40-ff7c-4625-9b78-cd11793f161f","GuestsOrExternalUsers"],"includeGroups":[],"excludeGroups":[],"includeRoles":[],"excludeRoles":[]}},"grantControls":{"operator":"OR","builtInControls":["block"],"customAuthenticationFactors":[],"termsOfUse":[]}}] -------------------------------------------------------------------------------- /demodata/withOnePerm.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": "97c23ace-8dba-4ff5-b79a-019d8b8f3a37", 3 | "displayName": "baseline", 4 | "createdDateTime": "2022-09-09T05:44:17.505053Z", 5 | "modifiedDateTime": "2022-09-09T07:05:28.2076491Z", 6 | "state": "enabled", 7 | "sessionControls": null, 8 | "conditions": { 9 | "userRiskLevels": [], 10 | "signInRiskLevels": [], 11 | "clientAppTypes": ["all"], 12 | "servicePrincipalRiskLevels": [], 13 | "platforms": null, 14 | "locations": null, 15 | "devices": null, 16 | "clientApplications": null, 17 | "applications": { 18 | "includeApplications": ["All"], 19 | "excludeApplications": ["Office365"], 20 | "includeUserActions": [], 21 | "includeAuthenticationContextClassReferences": [] 22 | }, 23 | "users": { 24 | "includeUsers": ["All"], 25 | "excludeUsers": ["259fcf40-ff7c-4625-9b78-cd11793f161f", "84ec66ab-9f97-4b8f-a173-9cf7e443ff87"], 26 | "includeGroups": [], 27 | "excludeGroups": [], 28 | "includeRoles": [], 29 | "excludeRoles": ["9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3"] 30 | } 31 | }, 32 | "grantControls": { 33 | "operator": "OR", 34 | "builtInControls": ["mfa"], 35 | "customAuthenticationFactors": [], 36 | "termsOfUse": [] 37 | } 38 | }, { 39 | "id": "a284f4d3-509b-4bff-bcef-17b61c210094", 40 | "displayName": "handle Admins", 41 | "createdDateTime": "2022-09-09T05:45:31.5747936Z", 42 | "modifiedDateTime": "2022-09-09T06:50:48.7431569Z", 43 | "state": "enabled", 44 | "sessionControls": null, 45 | "conditions": { 46 | "userRiskLevels": [], 47 | "signInRiskLevels": [], 48 | "clientAppTypes": ["all"], 49 | "servicePrincipalRiskLevels": [], 50 | "platforms": null, 51 | "locations": null, 52 | "devices": null, 53 | "clientApplications": null, 54 | "applications": { 55 | "includeApplications": ["All"], 56 | "excludeApplications": [], 57 | "includeUserActions": [], 58 | "includeAuthenticationContextClassReferences": [] 59 | }, 60 | "users": { 61 | "includeUsers": [], 62 | "excludeUsers": [], 63 | "includeGroups": ["a7a34ae9-b8ce-4c9e-af7b-d32790734b18"], 64 | "excludeGroups": [], 65 | "includeRoles": ["9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3"], 66 | "excludeRoles": [] 67 | } 68 | }, 69 | "grantControls": { 70 | "operator": "OR", 71 | "builtInControls": ["mfa"], 72 | "customAuthenticationFactors": [], 73 | "termsOfUse": [] 74 | } 75 | }] -------------------------------------------------------------------------------- /docs/example.md: -------------------------------------------------------------------------------- 1 | # Resolver notes 2 | 3 | **Scenario 1** 4 | 5 | How group and users are resolved? 6 | 7 | aldo.diaz@m.dewi.red 8 | 9 | part of Marketing 10 | part of Sales 11 | -> Sales is part of group GlobalSales 12 | 13 | 14 | Baseline policy 15 | excludes: 16 | Marketing group 17 | Aldo Diaz User 18 | 19 | Sales Policy 20 | includes: 21 | GlobalSales group 22 | 23 | 24 | 25 | 26 | **Results** 27 | Policy | Terminations | lookup 28 | -|-|- 29 | All| 0| users:Marketing ``❌ While there are users who are terminated in other policies belonging to marketing, no policy targets 'Marketing' directly, or via nested group assignment`` 30 | All| 1| users:All - ``✅ 'Global Policy' inlcudes all users`` 31 | All| 1| users:Aldo Díaz ``✅ Aldo is matched in 'Sales Policy' via belonging to group 'Global Sales' via sub group 'Sales'`` 32 | All| 1| users:GuestsOrExternalUsers ``✅ 'Global Policy' inlcudes all users`` 33 | All| 2| users:Global Sales ``✅ 'Sales Policy' inlcudes all 'GlobalSales'`` 34 | 35 | **Scenario 2** 36 | 37 | What if Aldo happens to be in some Azure AD Role? 38 | > Admin roles are always shown separately and not mapped to users and groups, this is deliberate design decisions for the time being to ensure that role inclusions and exclusions are shown explicitly. It also helps understanding situations where user might be eligible for a role, and thus showing both mapping individually should bring some clarity to results 39 | 40 | aldo.diaz@m.dewi.red 41 | 42 | Role:Attack Simulation Administrator 43 | part of Marketing 44 | part of Sales 45 | -> Sales is part of group GlobalSales 46 | 47 | 48 | Baseline policy 49 | excludes: 50 | Marketing group 51 | Aldo Diaz User 52 | Attack Simulation Administrator Role 53 | 54 | Sales Policy 55 | includes: 56 | GlobalSales 57 | 58 | 59 | 60 | 61 | **Results** 62 | Policy | Terminations | lookup 63 | -|-|- 64 | All| 0| users:Attack Simulation Administrator ``❌ While 'User Aldo' is part of this role, it is only shown, that this role is not terminated`` 65 | All| 0| users:Marketing ``❌ While there are users who are terminated in other policies belonging to marketing, no policy targets 'Marketing' directly, or via nested group assignment`` 66 | All| 1| users:All - ``✅ 'Global Policy' inlcudes all users`` 67 | All| 1| users:Aldo Díaz ``✅ Aldo is matched in 'Sales Policy' via belonging to group 'Global Sales' via sub group 'Sales'`` 68 | All| 1| users:GuestsOrExternalUsers ``✅ 'Global Policy' inlcudes all users`` 69 | All| 2| users:Global Sales ``✅ 'Sales Policy' inlcudes all 'GlobalSales'`` 70 | 71 | 72 | **Scenario 3** 73 | 74 | What if user is included in main exclusion group, but then handled via other group in policies that "close the gap"? In these situations when you want to explicitly show how the user/users might be terminated in other policies, you can use the [``--expand``](../readme.md#parameters) option. 75 | -------------------------------------------------------------------------------- /img/20220913101911.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsa2/caOptics/51b5374484005b0164af85258d34ca472c4b267b/img/20220913101911.png -------------------------------------------------------------------------------- /img/20220913102419.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsa2/caOptics/51b5374484005b0164af85258d34ca472c4b267b/img/20220913102419.png -------------------------------------------------------------------------------- /img/20220913102436.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsa2/caOptics/51b5374484005b0164af85258d34ca472c4b267b/img/20220913102436.png -------------------------------------------------------------------------------- /img/20220913110303.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsa2/caOptics/51b5374484005b0164af85258d34ca472c4b267b/img/20220913110303.png -------------------------------------------------------------------------------- /img/20220913111008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsa2/caOptics/51b5374484005b0164af85258d34ca472c4b267b/img/20220913111008.png -------------------------------------------------------------------------------- /img/controlfolder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsa2/caOptics/51b5374484005b0164af85258d34ca472c4b267b/img/controlfolder.png -------------------------------------------------------------------------------- /init.sh: -------------------------------------------------------------------------------- 1 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash 2 | 3 | export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" 4 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm 5 | 6 | nvm install 14 7 | 8 | nvm alias default 14 9 | 10 | # Clone the project 11 | 12 | git clone https://github.com/jsa2/caOptics; 13 | cd caOptics; 14 | npm install; 15 | # -------------------------------------------------------------------------------- /logic.md: -------------------------------------------------------------------------------- 1 | 1. Pure exclusions : Try to find exact matching policies that include exclusion 2 | 2. Exclusions by inclusion: Try to find exact match for the exclusion created by inclusion (in order for mutations to be created these exclusions like apps and locations, need to be injected in the pipeline) 3 | 3. Try to find catch-all policy when 1,2 cannot satisfy the pipeline 4 | 5 | 6 | 10 | 11 | 12 | # Progress 13 | There can be situation, where the gap is created by combination of policies, in these cases the all permutations need to be used to do lookup of matches. 14 | 15 | ## Gap detection done! 16 | 17 | 18 | ## Other 19 | Ensure policies that target just devices are /can be filtered out 20 | Remove Security Information registration policies 21 | 22 | 23 | ## Ensure auth works 24 | add AZ CLI "piggyback" 25 | 26 | ## map ID's in report to actual names 27 | Done 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caoptics", 3 | "version": "1.0.0", 4 | "main": "main.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "dependencies": { 13 | "axios": "^1.6.7", 14 | "chalk": "^4", 15 | "js-beautify": "^1.14.11", 16 | "jsonwebtoken": "^9.0.2", 17 | "uuid": "^9.0.1", 18 | "yargs": "^17.7.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plugins/evallIst.js: -------------------------------------------------------------------------------- 1 | 2 | async function evalLists (itemType, policies,currentPolicy) { 3 | 4 | let innerPol = policies 5 | let lookup =itemType.split(':') || [] 6 | 7 | try { 8 | 9 | return await require(`../ca/conditions/eval/${lookup[0]}.js`)(lookup, policies, currentPolicy) 10 | 11 | } catch (error) { 12 | console.log(error) 13 | return [] 14 | } 15 | 16 | 17 | } 18 | 19 | module.exports={evalLists} -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Note 3 | 4 | 5 | **Project archived due to shifting development priorities and refocusing my community efforts to other areas (PoCs, other tools and demos/presentation), project is set to read-only.** 6 | 7 | 8 | --- 9 | 10 | - [CA Optics - Azure AD Conditional Access Gap Analyzer](#ca-optics---azure-ad-conditional-access-gap-analyzer) 11 | - [What is Conditional Access?](#what-is-conditional-access) 12 | - [Notes for early testers](#notes-for-early-testers) 13 | - [Release notes](#release-notes) 14 | - [Documentation](#documentation) 15 | - [Example of a gap](#example-of-a-gap) 16 | - [Permutation generation](#permutation-generation) 17 | - [Prerequisites](#prerequisites) 18 | - [Important](#important) 19 | - [Description - Conditional Access Gap analyzer](#description---conditional-access-gap-analyzer) 20 | - [Compared to existing tooling](#compared-to-existing-tooling) 21 | - [Scope](#scope) 22 | - [Opinionated design](#opinionated-design) 23 | - [Design decisions](#design-decisions) 24 | - [Platform lookup](#platform-lookup) 25 | - [Lookup differences](#lookup-differences) 26 | - [Group nesting](#group-nesting) 27 | - [Parameters](#parameters) 28 | - [supplying parameters from launch.json (debugging in VSCode)?](#supplying-parameters-from-launchjson-debugging-in-vscode) 29 | - [Running the tool](#running-the-tool) 30 | - [Viewing reports](#viewing-reports) 31 | - [Troubleshooting](#troubleshooting) 32 | - [Contributing](#contributing) 33 | 34 | --- 35 | 36 | 37 | # CA Optics - Azure AD Conditional Access Gap Analyzer 38 | 39 | Azure AD Conditional Access Gap Analyzer is a solution for scanning gaps that might exist within complex Azure Active Directory Conditional Access Policy setups. 40 | 41 | 42 | ## What is Conditional Access? 43 | 44 | If you are new to Conditional Access we recommend that you review the following Microsoft article: https://docs.microsoft.com/en-us/azure/active-directory/conditional-access/overview 45 | 46 | --- 47 | 48 | ## Notes for early testers 49 | 50 | --- 51 | One-liner to run this tool: (if this is the only part you are planning to read, and have completed installation) 52 | 53 | ``node ./ca/main.js --mapping --skipObjectId=259fcf40-ff7c-4625-9b78-cd11793f161f --clearPolicyCache --clearTokenCache --clearMappingCache `` 54 | 55 | --- 56 | 57 | 58 | After completing the pre-requisites and reading this readme file, consider following: 59 | 60 | 1. reportOnly policies are not considered terminating: 61 | Read: [``scope``](#scope) 62 | 63 | 1. run each scan with [``--clearPolicyCache``](#parameters) 64 | 2. run each scan with [``--clearMappingCache``](#parameters) if you do changes in the groups / users related to policies 65 | 66 | 3. only policies targeting users and apps are in scope (this is the most common scope, but means for example, that security registration policy is not evaluated) 67 | Read: [``scope``](#scope) 68 | 69 | 1. Start with test environment so you get some experience and can set expectations about the tool mechanics 70 | 2. if you have known group or users that are excluded from policies define with ``--skipObjectIds`` objects to be excluded from the scan unless you are looking to confirm the exclusions 71 | 3. If you are running scans in multiple environments ensure: logins and caches are removed before running new scans 72 | 73 | Read:[parameters](#parameters) 74 | - if you have AZ CLI installed, then clear AZ CLI cache before proceeding with ``az account clear`` and new perform new login to the environment you are planning to scan with ``az login`` 75 | 76 | Read [other important notes](#important) 77 | 78 | --- 79 | 80 | # Release notes 81 | 82 | Release notes: 0.7.1 83 | - Updated depedencies and report text outputs 84 | Release notes: 0.7 85 | - Uses beta endpoint now by default, --expand option now expands the results to report regardless of the use of --allTerminations 86 | Release notes: 0.6.9 87 | - using --expand=9c06d103-f5b0-4404-bb25-aec4636912cd,47087cd3-64e9-470b-980a-5662f498e016 and expand 10 group members to for separate inspection. 88 | Release notes: 0.6.8 89 | - When you update policy with any guest conditions in GUI that policy will be only available from the beta endpoint after the update (during preview). 90 | - This update brings normalization for policies that are transfered to beta endpoint due to this behavior. 91 | - The policy will be evaluated like the previous guest conditions, as long as the following conditions are included "internalGuest,b2bCollaborationGuest,b2bCollaborationMember" and no tenants are excluded from the policy. In order to evaluate transferred policies, 92 | - use '--allowPreviewPolicies' when running CaOptics to account for this behavior 93 | 94 | Release notes: 0.6.6-7 beta 95 | - Allow use of different login endpoints for login and graph with params: --altLogin --altGraph 96 | - Allow use of custom filtering for policies (this only recommended, when the policies do not adhere to expected schema) 97 | 98 | Release notes: 0.6.5 beta 99 | - Added counter to reporting when high number of permutations is also added to report (default is to add only unterminated) 100 | - Minor code fixes changing to 101 | - Report filename now includes day, month, year and tenantId e.g. report_day_4_month_9_year_2022-tenant_48f55450-183a-45d6-a9ce-68f3cbc68947.csv 102 | 103 | Release notes: 0.6.4 beta 104 | - Get more groups per single call (less batching) 105 | - Fix race condition detected when generally using for await loops 106 | - Enclose values with "" between delimitters (CSV) 107 | 108 | Release notes: 0.6.3 beta 109 | - Optimizations to way the mapped objects are handled. 110 | - Mapped objects are cached. You can recreate the object mapping by using parama 'clearMappingCache' 111 | - Lookup keys will start from 'user/group/role' conditions always first 112 | - Added possibility to populate usermap with random UUID's to test for performance impact (this just debug option, and not really something that would be in non-beta versions) 113 | 114 | Release notes: 0.6.2 beta 115 | - Separated cache params into separate functions -> (clearTokenCache and ClearPolicyCache) 116 | - Added possibility of running pre-optimized algorithm on permutations with param --aggressive (High memory consumption, only here for A/B testing) 117 | - merge completed. 118 | 119 | Release notes: 0.6.1 beta 120 | - Basic version of CSV reporting added 121 | - Streamlined permutation generation to ensure essential permutations are generated, and some permutations are are terminated earlier on the lookups 122 | 123 | Release notes: 0.6 beta (first non "silent" release) 124 | - App displayNames added to MD report. Object type added to the user type 125 | 126 | Release notes: 0.5.2,0.5.1,0.5 beta (see previous branches for release notes) 127 | 128 | --- 129 | 130 | 131 | 132 | --- 133 | # Documentation 134 | ## Example of a gap 135 | 136 | ![](img/20220913111008.png) 137 | 138 | **Example of cross-policy detection** 139 | 140 | ❌ Any permutation with value 0 means that no policies was terminated for that particular combination of conditions. 141 | 142 | Policy | Terminations | lookup 143 | -|-|- 144 | All| 0| Applications:88cc92be-d474-4d95-a57d-7b3ef701f510 -> Locations:finland -> users:GuestsOrExternalUsers 145 | All| 0| Applications:88cc92be-d474-4d95-a57d-7b3ef701f510 -> Locations:finland -> users:Jane Doe 146 | All| 0| Applications:88cc92be-d474-4d95-a57d-7b3ef701f510 -> Locations:finland -> users:John Doe 147 | 148 | 149 | 150 | ✅ Read detailed description of detection in [``docs/example.md``](/docs/example.md) 151 | 152 | 153 | 154 | --- 155 | 156 | ## Permutation generation 157 | 158 | Permutation are generated by [``getPol2.js``](ca/mainPlugins/getPol2.js) 159 | - How it works? Recursive search is performed for all conditions and then conditions are placed under unique permutations 160 | 161 | 162 | 163 | [Permutations can be visualized with JSON Crack service:](https://jsoncrack.com/editor?json=%5B%5B%22rootItem%22%2C%22subItems%22%2C%22a%7C0%7C1%22%2C%22users%3AGuestsOrExternalUsers%22%2C%22clientAppTypes%3Abrowser%22%2C%22Locations%3A1deda895-c55a-474f-ae90-3cf0c4316149%22%2C%22Applications%3A88cc92be-d474-4d95-a57d-7b3ef701f510%22%2C%22Platforms%3AiOS%22%2C%22a%7C%22%2C%22o%7C2%7C7%7C8%22%2C%22Platforms%3AAll%22%2C%22o%7C2%7CA%7C8%22%2C%22a%7C9%7CB%22%2C%22o%7C2%7C6%7CC%22%2C%22Applications%3AAll%22%2C%22o%7C2%7CE%7CC%22%2C%22a%7CD%7CF%22%2C%22o%7C2%7C5%7CG%22%2C%22Locations%3AAll%22%2C%22o%7C2%7CI%7CG%22%2C%22a%7CH%7CJ%22%2C%22o%7C2%7C4%7CK%22%2C%22clientAppTypes%3AmobileAppsAndDesktopClients%22%2C%22o%7C2%7CM%7CK%22%2C%22a%7CL%7CN%22%2C%22o%7C2%7C3%7CO%22%2C%22users%3AAll%22%2C%22o%7C2%7CQ%7CO%22%2C%22a%7CP%7CR%22%5D%2C%22S%22%5D) 164 | 165 | >(Visualization by https://jsoncrack.com/) 166 | 167 | 168 | 169 | >![](20220929154543.png) 170 | 171 | 172 | 173 | ## Prerequisites 174 | 175 | **Runtime** 176 | - Node.JS 14 LTS (Linux) ([Install in Linux](https://github.com/nvm-sh/nvm#install--update-script)) 177 | - Node.JS 16 LTS (Windows) ([Install in Windows](https://docs.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-windows#install-nvm-windows-nodejs-and-npm)) 178 | 179 | **If you prefer not to use the fire & forget version** 180 | ```bash 181 | nvm use 16 182 | git clone https://github.com/jsa2/caOptics; 183 | cd caOptics; 184 | npm install; 185 | ``` 186 | 187 | **Fire and forget run setup for Azure Cloud Shell (Bash)** 188 | 189 | ```bash 190 | curl -o- https://raw.githubusercontent.com/jsa2/caOptics/main/init.sh | bash; 191 | # Force reload of NVM 192 | export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" 193 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 194 | # This loads nvm 195 | ``` 196 | 197 | ```bash 198 | cd caOptics 199 | # Force fresh login (AZ CLI can't use the built-in token for the scope we are looking in here) 200 | az login 201 | ``` 202 | 203 | **Azure AD Related** 204 | - Azure AD Security Reader role enabled 205 | - If have Azure CLI and existing CLI session, this tool will use that [session](tokenHandler/getCode.js). 206 | - If you don't have Azure CLI installed just Azure CLI clientID is used to get tokens with device_code flow initated by the Node.js http client 207 | 208 | >![](img/20220913102436.png) 209 | 210 | 211 | **Following open source packages are used** 212 | 213 | To reduce amount of code, we use the following depedencies for operation and aesthetics are used (Kudos to the maintainers of these fantastic packages. Each packages license link is supplied in the license column) 214 | 215 | package | aesthetics|operation|license 216 | -|-|-|- 217 | [axios](https://www.npmjs.com/package/axios)||✅ | [MIT](https://github.com/axios/axios/blob/v1.x/LICENSE) 218 | [yargs](https://www.npmjs.com/package/yargs)||✅ | [MIT](https://github.com/yargs/yargs/blob/main/LICENSE) 219 | [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) | |✅|[MIT](https://github.com/auth0/node-jsonwebtoken/blob/master/LICENSE) 220 | [chalk](https://www.npmjs.com/package/chalk)| ✅ | |[MIT](https://github.com/chalk/chalk/blob/main/license) 221 | [js-beautify ](https://www.npmjs.com/package/js-beautify) | ✅ | |[MIT](https://github.com/beautify-web/js-beautify/blob/main/LICENSE) 222 | 223 | **What network access is needed for this to work?** 224 | 225 | 1. Following hosts are needed for operation 226 | 227 | ```sh 228 | graph.microsoft.com 229 | login.microsoftonline.com 230 | ``` 231 | 232 | 2. Before operation access to github and npmjs is needed to download depedencies. 233 | > If you plan to run this tool in network restricted environment, then download the depedencies first in an environment that allows access to package installations and github.com. You may then transfer the whole installation directory zipped to the network restricted environment 234 | 235 | > Below is typical trace I do when I am running any 3rd party packages on my Node.js apps. It shows the URL's that are being called in runtime 236 | 237 | ![](20220930153822.png) 238 | 239 | --- 240 | 241 | ## Important 242 | 243 | 1. Read the [License](LICENSE) 244 | 245 | 2. ⚠️ No Input sanitization is performed on launch params, as it is always assumed, that the input of these parameters are controlled, and this tool does not run in uncontrolled environment (or unattended). While I have not reviewed all paths, I believe that achieving shellcode execution is trivial. This tool does not assume hostile input, thus the recommendation is that you don't paste launch arguments into command line without reviewing them first. 246 | 3. We recommend that this tool is always run only by **read-only permissions** (if you have AZ CLI installed, remove AZ CLI cache before proceeding ``az account clear``) 247 | 4. Legacy auth is not evaluated when the evaluated policy includes **only** legacy auth conditions - **Backround**: Microsoft is in the process of deprecating basic auth for Exchange, so large part of the legacy auth evaluation will soon (end of 2022) become irrelevant. 248 | >You can still opt for ``--includeLegacyAuth`` parameter to include only legay auth policies in the mix. Bare in mind, that this will assume, then that Legacy Auth is covered for all apps (thus evaluate it also for apps not supporting legacy auth), not only EXO 249 | 5. Tooling stores AZ CLI refresh token locally to retain session persistence - Tokens are cached locally in plaintext (just like they are cached with Azure CLI in BASH, regardless you use this tool or not). While token cache can be encrypted, it does not offer any benefits for the PoC for this time. Encrypting the token cache would not help that much either, as possibly many other tools/apps running in the system store opaque refresh tokens in their plaintext format. 250 | 6. Regarding the reporting and permutations: I am still working figuring out the best balance between readability and verbosity. I do believe that all gaps in scope are catched, but I've done numerous changes to algorithm of such detections, and thus there could still be some edge conditions I have not considered. 251 | 252 | 253 | --- 254 | 255 | 256 | ## Description - Conditional Access Gap analyzer 257 | 258 | This tool solves the problem of finding gaps in Conditional Access Policies, even when the gap would not appear in sign-in logs. 259 | 260 | ### Compared to existing tooling 261 | 262 | How is this tool different from the existing tools? 263 | 264 | Tool | BPA ¹ | Gap analytics | Additional requirements 265 | -|-|-|- 266 | [``Microsoft Azure AD Assessment``](https://github.com/AzureAD/AzureADAssessment/) | ✅ | - Only in relation to BPA | new app registration 267 | [``Conditional access gap analyzer workbook`` ](https://docs.microsoft.com/en-us/azure/active-directory/reports-monitoring/workbook-conditional-access-gap-analyzer)| - | ² Only when the gap can attributed to sign-in event | logs are exported to Log Analytics workspace 268 | [``CA Optics``]() | - | ✅ |³ Access to Azure Cloud Shell and permissions to read CA policies via MS Graph API 269 | 270 | 271 | **additional** 272 | 273 | >¹ Best Practices analyzer 274 | 275 | >² Gap can't be identified if conditions that expose the gap have never recorded to sign-in log 276 | 277 | >³ Cloud Shell is not strict requirement, but good alternative for users of this tool, that might not have Node.js installed 278 | 279 | 280 | ## Scope 281 | At version 0.5 the conditions that are covered are as follows: 282 | 283 | ✅``Users / Roles /Groups`` 284 | 285 | ✅``Cloud apps`` (only policies that define all apps, or single apps are in scope) 286 | 287 | ✅``Device platforms`` (e.g. user-agents) 288 | 289 | ✅``Locations`` 290 | 291 | ✅``Client Apps`` (e.g. browser / desktop & mobile apps) ¹ 292 | 293 | ✅``Access Controls (Grant / Block)`` (Only policies that have Access Controls enabled are in scope ²) 294 | 295 | - These conditions were chosen as starting point, as most of the typical attacker abusable gaps occur in these policies. As we gain more experiences from testing of this tool we will introduce new gap detections. 296 | - >(Anything that is not on the list, is not evaluated. eg. risk based policies are not considered covering gaps, as the risk detection is not considered perfect, and should be used rather in situations you want to block something vs. require MFA only when there is risk associated) 297 | 298 | 299 | >¹ Given that Microsoft is deprecating Legacy Auth, legacy auth is not reviewed 300 | >² Policy enforcement needs to be enabled (report only policies are not evaluated by default, but can included with certain flag) 301 | 302 | 303 | ## Opinionated design 304 | 305 | It is important to understand that the tool reflects its creators opinions in policy design: 306 | 307 | There are two schools of Conditional Access design 308 | 1. > Include based: Only apply CA to conditions that match the predicted use of organization use patterns. 309 | 2. > Exclude based: Apply CA in all conditions, and then create narrow exclusions to handle these exceptions. 310 | 311 | ⚠ This tool only works for the latter school of CA design. The basis of this design is, that attacker considers all access patterns valid, even ones that organization might not consider valid for their own use. If your usage patterns does not fit the approach taken by this tool, we recommend that you consider instead the use of [existing tooling](#compared-to-existing-tooling) 312 | 313 | --- 314 | 315 | This school of thought is similar to Microsoft Best Practice ['Apply Conditional Access policies to every app'](https://docs.microsoft.com/en-us/azure/active-directory/conditional-access/plan-conditional-access#apply-conditional-access-policies-to-every-app 316 | ) with the distinction to the following statement, that besides apps all, also all other configured conditions should be covered by a policy, or certain type exclusion ¹ 317 | 318 | >*Ensure that every app has at least one Conditional Access policy applied. From a security perspective it's better to create a policy that encompasses All cloud apps, and then exclude applications that you don't want the policy to apply to. This ensures you don't need to update Conditional Access policies every time you onboard a new application.* 319 | 320 | 321 | >>¹ This tool considers gap to be covered also when trusted location, or trusted device exclusion is used, and policy is not thus applied due to use of trusted device or location. It is worth saying, that there are admin use best practices, that should require the trusted location or trusted device to be combined with MFA. 322 | 323 | --- 324 | 325 | ### Design decisions 326 | 327 | --- 328 | #### Platform lookup 329 | 330 | Current Design requires that all conditions are matched with 'All' platforms policy. This is based on the scenario, where all clients are selected explicitly, and thus no "unknown" clients are selected in this particular policy 331 | 332 | **Example of such condition** 333 | ```json 334 | "platforms": { 335 | "includePlatforms": ["all"], 336 | "excludePlatforms": ["macOS"] 337 | }, 338 | ``` 339 | 340 | **Reference** https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/concept-conditional-access-conditions#device-platforms 341 | 342 | 343 | > **Important**
*Microsoft recommends that you have a Conditional Access policy for unsupported device platforms. As an example, if you want to block access to your corporate resources from Chrome OS or any other unsupported clients, you should configure a policy with a Device platforms condition that includes any device and excludes supported device platforms and Grant control set to Block access.* 344 | 345 | --- 346 | 347 | #### Lookup differences 348 | 349 | 350 | 1. Role lookup 351 | > When roles are used as conditions lookup for exclusions/inclusions is dictated only by the role. This might result in situation, where the policy terminates correctly to a role, but user who is in that role might be still excluded from the policy in the results. In the results both conditions are highlighted (not merged). This is purely design decision, as we want to show role terminations seperately without user or group context. 352 | 353 | --- 354 | 355 | Example: 356 | userID:0cd1b62d-5ff8-497b-9b56-9bb02bc0ab8c is included from All Apps policy 357 | 358 | There is admin policy, that covers role of Global administrator, but not the userId. 359 | 360 | Both of following conditions would be shown in results: 361 | 1. Role GA is excluded individually (1) 362 | 2. UserId is not explicitly targeted by group or userId (0) 363 | 364 | All (cross-policy) policies matched:1 users:Company Administrator 365 | All (cross-policy) policies matched:0 users:joosua santasalo 366 | 367 | 368 | 369 | --- 370 | 371 | 2. Group lookup 372 | > Conversely if groups are used, the policy termination is merged, and so user exclusion would be shown as terminated by the group inclusion. 373 | 374 | Example: 375 | userID:194383eb-6053-4d1e-bc72-f332be6ca2cb is included from All Apps policy 376 | 377 | There is policy which targets the user only by group 378 | 379 | Following conditions would be shown in results: 380 | 1. User is excluded individually 381 | 2. User is targeted via group. 382 | 383 | 384 | All (cross-policy) policies matched:1 users:Vihtori Santasalo (matched via group) 385 | 386 | 387 | 3. Guest lookup 388 | 389 | >Users are not mapped as guests at this release. This feature will be introduced later enabling catching inclusions/exclusions of guest userId's to correctly mapped to ``GuestsOrExternalUsers`` property. 390 | Currently guest users are mapped just like normal users. 391 | > 392 | 393 | #### Group nesting 394 | 395 | >Due to performance considerations nesting depth for resolving membership of groups is only resolved once if group is group of another group. Otherwise there is risk of resolving memberships indefinitely, as subgroup might include any of the root groups, resulting in un-ending loop condition 396 | 397 | Membership: 398 | Group1 -> all members resolved 399 | --> user is member of Group1 400 | --> Group2 is member of Group1 -> all members resolved to Group1 401 | --> Group3 is member of Group1 -> NOT RESOLVED 402 | 403 | > If you dare... you can increase the nesting directly in the code... [group.js](ca/mainPlugins/userMappers/group.js) 404 | 405 | ![](img/20220913101911.png) 406 | 407 | 408 | 409 | ## Parameters 410 | 411 | Param| Description 412 | -|- 413 | ``mapping`` | By default user and group based exclusions are evaluated by objects exact ID. For example if object is excluded by one policy, the object should be found in another policy by it's exact id.
Using `` --mapping`` invokes MS Graph to populate relationship between objects in a way that ensures, that for example user can be excluded by userId in one policy, while the evaluation detects that user is included in another policy by a groupId.
e.g. ``--mapping`` 414 | ``clearMappingCache`` | Clears user / group mappings retrieved on earlier run
e.g ``--clearMappingCache`` 415 | ``skipObjectId `` | Removes ObjectId from permutations. This migth be useful, if you want to exclude break-glass account from results
e.g. ``--skipObjectId=bcd27e9b-8974-42ec-a2a1-ba2498b45674,c7a4a639-00e7-47e0-aa9d-ea502bbbd382`` 416 | ``skip `` | Removes full permutation category.
e.g. ``--skip=users`` 417 | ``includeReportOnly `` | Allows mixing reportOnly policies with enabled policies. Remove existing policies before running this by removing the policies.json file in the root, or use ``clearPolicyCache`` option
e.g. ``--includeReportOnly`` 418 | ``includeLegacyAuth `` | By default policies that have just legacy auth conditions are not evaluated. Use this flag if you want to include legacy auth into the analysis
please note that including legacy auth will review it against all apps, not only supported workloads (AAD,SPO,EXO)
e.g. ``--includeLegacyAuth`` 419 | ``clearPolicyCache `` | Removes policy caches
e.g. ``--clearPolicyCache`` 420 | ``clearTokenCache `` | Removes token caches
e.g. ``--clearTokenCache`` 421 | ``allTerminations`` | Includes also results that terminated to policy
``--allTerminations`` 422 | ``debug`` | Shows memory use and estimation of progress
eg. ``--debug`` 423 | ``altLogin `` | Allows defining alternative login endpoint FQDN
eg. ``--altLogin=login.microsoft.com.alt`` 424 | ``altGraph `` | Allows defining alternative graph endpoint FQDN
eg. ``--altGraph=graph.microsoft.com.alt`` 425 | ``customPolicyFilter `` | Allow use of custom filtering for policies (this only recommended, when the policies do not adhere to expected schema)
eg. ``--customPolicyFilter``
You can modify the filter by selecting [``customPolicyFilter.js``](ca/mainPlugins/customPolicyFilter.js) 426 | ``expand `` | Allows defining an group to be expanded in results. By default the group is expanded for 10 first items. Amount of expanded items can be increased by using in conjuction the option ``--expandCount=30``
eg. ``--expand=9c06d103-f5b0-4404-bb25-aec4636912cd,47087cd3-64e9-470b-980a-5662f498e016``
Note: Use of this option requires that mapping cache is cleared ``--clearMappingCache`` 427 | 428 | ### supplying parameters from launch.json (debugging in VSCode)? 429 | 430 | 431 | copy the current json schema and create file named launch.json in folder ``.vscode/launch.json`` 432 | 433 | ```json 434 | { 435 | "version": "0.2.0", 436 | "configurations": [ 437 | { 438 | "type": "node", 439 | "request": "launch", 440 | "name": "Launch Program", 441 | "skipFiles": [ 442 | "/**" 443 | ], 444 | "program": "${workspaceFolder}/ca/main.js", 445 | //"program": "${file}", 446 | "args": [ 447 | "--skipObjectId=259fcf40-ff7c-4625-9b78-cd11793f161f", 448 | "--mapping", 449 | "--clearMappingCache", 450 | "--clearPolicyCache", 451 | "--clearTokenCache", 452 | "--customPolicyFilter", 453 | "--allowPreviewPolicies=beta", 454 | // "--altGraph=graph.microsoft.com", 455 | // "--allPlatforms", 456 | // "--includeReportOnly", 457 | //"--inject", 458 | //"--clearPolicyCache", 459 | // "--skipCleaning", 460 | // "--allTerminations", 461 | // "--aggressive", 462 | "--debug", 463 | ], 464 | "runtimeArgs": [ 465 | "--max-old-space-size=4096" 466 | ] 467 | } 468 | ] 469 | } 470 | ``` 471 | 472 | ## Running the tool 473 | 474 | >Before running the tool first time 475 | 476 | ```sh 477 | #Navigate to the cloned folder 478 | cd caOptics; 479 | #Install depedencies 480 | npm install 481 | ``` 482 | 483 | Each run will initiate login if no session is stored. If you have session on Azure CLI and would like to use another session, then in Azure CLI run ``Az Account Clear`` 484 | 485 | 1. Static run (no [mapping](#parameters) ) 486 | >``node ./ca/main.js `` 487 | 2. Static run (with [mapping](#parameters) ) 488 | >``node ./ca/main.js --mapping`` 489 | 3. Supplying ``skipObjectId=yourID`` to exclude Break glass group 490 | >``node ./ca/main.js --mapping --skipObjectId=259fcf40-ff7c-4625-9b78-cd11793f161f`` 491 | 492 | Also, if you are experimenting with the tool, it is recommended to include ``--clearPolicyCache --clearTokencache`` param. This will remove existing session tokens, and the policy.json (cache) 493 | 494 | >remove policies.json from the project folder, if you don't want to remove session from cache, but just want to have policies to be removed 495 | 496 | **Expected output (based on example data)** 497 | 498 | Policy | Permutation | Terminations | lookup 499 | -|-|-|- 500 | All (cross-policy)| 33a51ecbcc58466a90b6bea66b239c74| 0| Locations:10a25087-9bf6-479b-a2a1-37e814310c90 -> Platforms:macOS -> clientAppTypes:mobileAppsAndDesktopClients -> users:All 501 | All (cross-policy)| 4194a5e64e054764bd8774d4992887b9| 0| Locations:10a25087-9bf6-479b-a2a1-37e814310c90 -> Platforms:macOS -> clientAppTypes:mobileAppsAndDesktopClients 502 | All (cross-policy)| 24f38821ea8e4331aeae7c078a727236| 1| Locations:10a25087-9bf6-479b-a2a1-37e814310c90 -> Platforms:macOS 503 | 504 | 505 | ## Viewing reports 506 | All successful runs output ``crosstable.md`` (a markdown table) and csv ``report.csv`` 507 | - Each new run of the tool will erase the previous report 508 | 509 | **Example of .csv report** 510 | 511 | ``` 512 | users Applications clientAppTypes Platforms locations terminations 513 | ----- ------------ -------------- --------- --------- ---------- 514 | GuestsOrExternalUsers All browser macOS finland 0 515 | user-Joosua Santasalo All browser macOS finland 0 516 | user-Joosua Santasalo All mobileAppsAndDesktopClients macOS finland 0 517 | All All browser macOS finland 0 518 | ``` 519 | 520 | **Example of .json dump** 521 | 522 | ```json 523 | [ 524 | { 525 | "policy": "all", 526 | "lineage": "Platforms:macOS -> clientAppTypes:browser -> users:All -> Locations:10a25087-9bf6-479b-a2a1-37e814310c90 -> ", 527 | "lookup": "Locations:10a25087-9bf6-479b-a2a1-37e814310c90", 528 | "terminated": [] 529 | }, 530 | { 531 | "policy": "all", 532 | "lineage": "Platforms:macOS -> clientAppTypes:browser -> users:GuestsOrExternalUsers -> Locations:10a25087-9bf6-479b-a2a1-37e814310c90 -> ", 533 | "lookup": "Locations:10a25087-9bf6-479b-a2a1-37e814310c90", 534 | "terminated": [] 535 | } 536 | ] 537 | ``` 538 | 539 | 540 | ## Troubleshooting 541 | 542 | - If you are using AZ CLI for session login, start with ``az account clear`` 543 | - Upon running the tool add the ``--clearPolicyCache --clearTokencache`` to clear caches (tokens, policies and locations) 544 | - remove existing policies.json and namedLocations.json from project root manually (if you are not using the ``--clearPolicyCache --clearTokencache`` option ) 545 | - If Group includes don't seem to work, ensure ``--mapping`` is selected, and you don't have nesting beyond two groups 546 | 547 | --- 548 | 549 | **Testing in Azure Cloud Shell** 550 | 551 | If you are testing this solution in larger environment. Enable ``--debug`` in parameters and if possible increase the node heap size `` --max-old-space-size=4096 `` 552 | - Testing experience has shown, that the amount of permutations can reach quite high numbers with large environments. running with debug mode can help pointing out where the issue might occur. In such scenarios using Cloud Shell might not be viable (though the permuation algorithm has now hugely reduced memory use, but I have not been able to confirm, that cloud shell would work in large environments) 553 | 554 | > Since the current algorithm for permutation generation is much reduced from original one, even Cloud Shell should work, but for example race condition, or other memory leak might still loom somewhere, and manifest in larger environments, mitigating the benefits of the current algorithm. 555 | 556 | ``node --max-old-space-size=4096 ./ca/main.js --mapping --skipObjectId=259fcf40-ff7c-4625-9b78-cd11793f161f --clearPolicyCache --debug`` 557 | 558 | 559 | --- 560 | 561 | - You might have empty references in CA policies and lookup will thus show unterminated condition 562 | 563 | >![](20220922085530.png) 564 | 565 | 566 | # Contributing 567 | 568 | Open a pull request, or submit a issue depending on the scope of the request. 569 | -------------------------------------------------------------------------------- /run.md: -------------------------------------------------------------------------------- 1 | # Quick run commands reference 2 | history 10 | cut -c 8- 3 | 4 | ```sh 5 | 6 | node ./ca/main.js --mapping --skipObjectId=259fcf40-ff7c-4625-9b78-cd11793f161f 7 | 8 | node ./ca/main.js --mapping --skipObjectId=259fcf40-ff7c-4625-9b78-cd11793f161f -clearPolicyCache 9 | 10 | node ./ca/main.js --mapping --skipObjectId=259fcf40-ff7c-4625-9b78-cd11793f161f -clearPolicyCache --clearTokenCache 11 | 12 | 13 | ``` 14 | 15 | 16 | # demos 17 | 18 | ## 19 | // should show 20 | 21 | all| 0 | users:GuestsOrExternalUsers -> Locations:All -> Platforms:android -> clientAppTypes:browser -> Applications:Office365 -> 22 | all| 0 | users:GuestsOrExternalUsers -> Locations:All -> Platforms:iOS -> clientAppTypes:browser -> Applications:Office365 -> 23 | all| 0 | users:GuestsOrExternalUsers -> Locations:All -> Platforms:windows -> clientAppTypes:browser -> Applications:Office365 -> 24 | all| 0 | users:GuestsOrExternalUsers -> Locations:All -> Platforms:windowsPhone -> clientAppTypes:browser -> Applications:Office365 -> 25 | all| 0 | users:GuestsOrExternalUsers -> Locations:All -> Platforms:linux -> clientAppTypes:browser -> Applications:Office365 -> 26 | all| 0 | users:GuestsOrExternalUsers -> Locations:All -> Platforms:All -> clientAppTypes:browser -> Applications:Office365 -> 27 | 28 | // 29 | git checkout tooling 30 | 31 | // remove current pols 32 | node DOnotRunThis.js 33 | 34 | // add scene extremelyNarrow 35 | cp demodata/extremelyNarrow.json policies.json 36 | 37 | // update 38 | node rollback.js 39 | 40 | //go back to main 41 | git checkout "0.6.5" 42 | 43 | // Run scan 44 | node ./ca/main.js --mapping --skipObjectId=259fcf40-ff7c-4625-9b78-cd11793f161f --clearPolicyCache --clearTokenCache --clearMappingCache 45 | 46 | ## 47 | cp demodata/policies.json policies.json 48 | node ./ca/main.js --mapping --skipObjectId=259fcf40-ff7c-4625-9b78-cd11793f161f --clearTokenCache --clearMappingCache 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /tokenHandler/axioshelpers.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const qs = require('querystring') 3 | 4 | async function axiosClient (options, urlencoded, debug) { 5 | 6 | if (urlencoded == true) { 7 | options.data = qs.stringify(options.data) 8 | } 9 | if (debug) { 10 | console.log(options) 11 | } 12 | 13 | var data = await axios(options).catch((error) => { 14 | 15 | return Promise.reject(error) 16 | 17 | }) 18 | 19 | return data 20 | 21 | } 22 | 23 | 24 | module.exports = {axiosClient} -------------------------------------------------------------------------------- /tokenHandler/getCode.js: -------------------------------------------------------------------------------- 1 | 2 | const chalk = require('chalk') 3 | const {argv} = require('yargs') 4 | const {axiosClient} = require('./axioshelpers') 5 | const tsto= require('util').promisify(setTimeout) 6 | 7 | 8 | async function azCLI () { 9 | 10 | var data={ 11 | client_id:argv.client || "04b07795-8ddb-461a-bbee-02f9e1bf7b46", 12 | scope: argv.scope || "openid offline_access" 13 | } 14 | 15 | var opt = { 16 | method:"post", 17 | url:`https://${argv.altLogin || "login.microsoftonline.com"}/${argv?.tid || "common"}/oauth2/v2.0/devicecode?api-version=1.0`, 18 | data 19 | } 20 | let errorP 21 | var at = await axiosClient(opt, true).catch((error) => { 22 | errorP =error 23 | }) 24 | 25 | if (errorP) { 26 | console.log(errorP) 27 | return; 28 | } 29 | 30 | console.log(chalk.green(at?.data?.message)) 31 | 32 | // console.log(at?.data) 33 | var i = 0 34 | do { 35 | 36 | data.grant_type="device_code", 37 | data.code=at?.data?.device_code 38 | opt.url=`https://${argv.altLogin || "login.microsoftonline.com"}/common/oauth2/v2.0/token` 39 | delete opt.data; opt.data = data 40 | 41 | i++ 42 | var loop = await axiosClient(opt,true).catch((error) => { 43 | console.log(chalk.bgCyan.whiteBright(error?.response?.data?.error)) 44 | }) 45 | await tsto(2000) 46 | /* console.log(i) 47 | console.log(i < 10) */ 48 | } while ( i < 15 && !loop?.data?.access_token) 49 | 50 | if (!loop?.data?.access_token) { 51 | throw new Error('Unable to retrieve access token') 52 | } 53 | console.log('iterations done') 54 | // console.log(loop?.data || 'no token') 55 | require('fs').writeFileSync('tokenHandler/rt.json',JSON.stringify(loop?.data)) 56 | return loop?.data?.access_token 57 | } 58 | 59 | module.exports= {azCLI} 60 | -------------------------------------------------------------------------------- /tokenHandler/readme.md: -------------------------------------------------------------------------------- 1 | # Azure Cloud Shell Client for device code flow 2 | 3 | Very simple implementation of Azure AD Device Code Flow using existing high privileged application (Azure CLI) 4 | - You can define your own application too 5 | 6 | 7 | ## Usage 8 | 9 | 1. Open Azure Cloud Shell (BASH) and paste following command to it: 10 | 11 | `` curl -o- "https://raw.githubusercontent.com/jsa2/aad_device_code/main/init.sh" | bash`` 12 | 13 | 2. Navigate to install directory ``cd aad_device_code/`` 14 | 3. Type ``npm install `` 15 | 4. Run the tool (It will wait for 15 iterations for login) 16 | `` node getCode.js --client=04b07795-8ddb-461a-bbee-02f9e1bf7b46 --resource=https://graph.microsoft.com `` 17 | 18 | - If you use your own clientID, and it is not an multitenant app, supply tenantId param too 19 | 20 | `` node getCode.js --tid=48f55450-183a-45d6-a9ce-68f3cbc68947 --client=b5505019-43a5-4eda-bc5e-b0157a1227b9 --resource=https://graph.microsoft.com `` 21 | 22 |
23 | 24 | ![image](https://user-images.githubusercontent.com/58001986/164604283-57cb6bb8-6a57-4890-b964-5170777cb070.png) 25 | 26 | ## Backround 27 | If you want to read about how this might be used for malicious purposes, read the excellent write-up by [DrAzureAD](https://twitter.com/DrAzureAD) 28 | @ https://o365blog.com/post/phishing/ -------------------------------------------------------------------------------- /tokenHandler/refresh.js: -------------------------------------------------------------------------------- 1 | const {argv} = require('yargs') 2 | const {axiosClient} = require('./axioshelpers') 3 | const waitForIt = require('util').promisify(setTimeout) 4 | 5 | async function rToken () { 6 | 7 | let refresh_token = require('./rt.json')?.refresh_token 8 | 9 | let data={ 10 | client_id:argv.client || "04b07795-8ddb-461a-bbee-02f9e1bf7b46", 11 | scope: argv.scope || "Directory.AccessAsUser.All", 12 | refresh_token, 13 | grant_type:"refresh_token" 14 | } 15 | 16 | let opt = { 17 | method:"post", 18 | url:`https://${argv.altLogin || "login.microsoftonline.com"}/common/oauth2/v2.0/token`, 19 | data 20 | } 21 | 22 | let {data:tokenResponse} = await axiosClient(opt,true).catch((error) => { 23 | console.log(error?.response?.data) 24 | }) 25 | 26 | require('fs').writeFileSync('./tokenHandler/token.json',JSON.stringify(tokenResponse?.access_token)) 27 | 28 | // when getting access token wait for a certain time before continuing 29 | await waitForIt(500) 30 | return tokenResponse?.access_token 31 | 32 | 33 | } 34 | 35 | module.exports={rToken} --------------------------------------------------------------------------------