有没有办法在不使用 Node-Red 中的 ROPC 身份验证流的情况下将 Microsoft Identiy Platform 与 OAuth 2.0 流一起使用?我不能使用 ROPC,因为目标租户强制执行 MFA。强制执行 MFA 时,ROPC 将被阻止。
我找到了插件node-red-contrib-oauth2,但无法通过除 ROPC 之外的另一个 OAuth 2.0 流程与 Microsoft Identity Platform 一起使用。
有没有办法在不使用 Node-Red 中的 ROPC 身份验证流的情况下将 Microsoft Identiy Platform 与 OAuth 2.0 流一起使用?我不能使用 ROPC,因为目标租户强制执行 MFA。强制执行 MFA 时,ROPC 将被阻止。
我找到了插件node-red-contrib-oauth2,但无法通过除 ROPC 之外的另一个 OAuth 2.0 流程与 Microsoft Identity Platform 一起使用。
解决方案是使用Device Code Flow。以下说明为您提供了一个流程,该流程能够使用节点红色读取您的Microsoft Teams 在线状态/ Microsoft Office 365 在线状态。
在下面的示例中,我们将创建一个只能读取登录用户状态的应用程序。这意味着,API 权限可能会根据您的需要而有所不同。
解释:
现在这个应用程序可以在 Node-Red 中用于从 MS Graph API 读取存在。
这种流程的一个很好的起点是:
[{"id":"7c76e545.92af9c","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"40792ca0.843c24","type":"http request","z":"7c76e545.92af9c","name":"","method":"POST","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","authType":"","x":710,"y":160,"wires":[["44c485e1.fa8d2c","643ed329.5bdfec"]]},{"id":"419648fb.f1a818","type":"inject","z":"7c76e545.92af9c","name":"launch device code request","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":160,"wires":[["427185e9.4132fc"]]},{"id":"657e48c9.bcda48","type":"function","z":"7c76e545.92af9c","name":"Set refresh_token","func":"flow.get('refresh_token', function(err, refresh_token) {\n if (err) {\n node.error(err, msg);\n } else {\n // initialise the counter to 0 if it doesn't exist already\n refresh_token = msg.payload.refresh_token;\n // store the value back\n flow.set('refresh_token',refresh_token, function(err) {\n if (err) {\n node.error(err, msg);\n } else {\n // make it part of the outgoing msg object\n msg.refresh_token = refresh_token;\n // send the message\n node.status({fill:\"green\",shape:\"dot\",text:`refresh_token: ${msg.refresh_token}`});\n node.send(msg);\n }\n });\n }\n});\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":990,"y":340,"wires":[[]]},{"id":"d1253feb.c9362","type":"function","z":"7c76e545.92af9c","name":"Set access_token","func":"flow.get('access_token', function(err, access_token) {\n if (err) {\n node.error(err, msg);\n } else {\n // initialise the counter to 0 if it doesn't exist already\n access_token = msg.payload.access_token;\n // store the value back\n flow.set('access_token',access_token, function(err) {\n if (err) {\n node.error(err, msg);\n } else {\n // make it part of the outgoing msg object\n msg.access_token = access_token;\n // send the message\n node.status({fill:\"green\",shape:\"dot\",text:`access_token: ${msg.access_token}`});\n node.send(msg);\n }\n });\n }\n});\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":990,"y":300,"wires":[[]]},{"id":"44c485e1.fa8d2c","type":"function","z":"7c76e545.92af9c","name":"Set device_code","func":"flow.get('device_code', function(err, refresh_token) {\n if (err) {\n node.error(err, msg);\n } else {\n // initialise the counter to 0 if it doesn't exist already\n device_code = msg.payload.device_code;\n // store the value back\n flow.set('device_code',device_code, function(err) {\n if (err) {\n node.error(err, msg);\n } else {\n // make it part of the outgoing msg object\n msg.device_code = device_code;\n // send the message\n node.status({fill:\"green\",shape:\"dot\",text:`device_code: ${msg.device_code}`});\n node.send(msg);\n }\n });\n }\n});\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":950,"y":160,"wires":[[]]},{"id":"648d5fe3.74d0b","type":"function","z":"7c76e545.92af9c","name":"","func":"var context = flow.get(['tenant_id','client_id','scope','device_code']);\nvar tenant_id = context[0];\nvar client_id = context[1];\nvar scope = context[2];\nvar device_code = context[3];\n\nif(!device_code)\n{\n msg.delay = 5*1000;\n return [msg, null];\n}\n\nif(tenant_id && client_id && scope && device_code)\n{\n msg.url = \"https://login.microsoftonline.com/\"+tenant_id+\"/oauth2/v2.0/token\";\n msg.headers = { \"Content-Type\": \"application/x-www-form-urlencoded\"};\n msg.payload = {\n \"client_id\": client_id,\n \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",\n \"scope\": scope,\n \"code\": device_code\n }\n node.status({fill:\"green\",shape:\"dot\",text:`device_code: ${device_code.substring(0, 10)}`});\n return [null, msg];\n}\n","outputs":2,"noerr":0,"initialize":"","finalize":"","x":320,"y":320,"wires":[["ddaa0b36.e42108"],["1fc0a330.6fe22d"]]},{"id":"ec8a6999.9abcd8","type":"inject","z":"7c76e545.92af9c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":130,"y":320,"wires":[["648d5fe3.74d0b"]]},{"id":"1fc0a330.6fe22d","type":"http request","z":"7c76e545.92af9c","name":"","method":"POST","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","authType":"","x":510,"y":320,"wires":[["d32c769a.1c42b8"]]},{"id":"ba3c9093.320a7","type":"comment","z":"7c76e545.92af9c","name":"Retrieve tokens ...","info":"... after login has been made in a browser","x":130,"y":260,"wires":[]},{"id":"d4b1dae2.c6c538","type":"comment","z":"7c76e545.92af9c","name":"refresh tokens every 30 minutes","info":"","x":170,"y":400,"wires":[]},{"id":"396bdeb0.93db72","type":"inject","z":"7c76e545.92af9c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1800","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":130,"y":440,"wires":[["8739bad6.405738"]]},{"id":"8739bad6.405738","type":"function","z":"7c76e545.92af9c","name":"refresh request","func":"var context = flow.get(['tenant_id','client_id','scope','refresh_token']);\nvar tenant_id = context[0];\nvar client_id = context[1];\nvar scope = context[2];\nvar refresh_token = context[3];\n\nmsg.url = \"https://login.microsoftonline.com/\"+tenant_id+\"/oauth2/v2.0/token\"; \nmsg.headers = {\n \"Content-Type\": \"application/x-www-form-urlencoded\"\n};\nmsg.payload = {\n \"grant_type\": \"refresh_token\",\n \"client_id\": client_id,\n \"refresh_token\": `${refresh_token}`,\n \"scope\": scope\n\n};\n\nif(tenant_id && client_id && scope && refresh_token )\n return msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":340,"y":440,"wires":[["1aafbb4a.e050b5"]]},{"id":"1aafbb4a.e050b5","type":"http request","z":"7c76e545.92af9c","name":"","method":"POST","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","authType":"","x":550,"y":440,"wires":[["d32c769a.1c42b8"]]},{"id":"97a36414.d2b318","type":"http request","z":"7c76e545.92af9c","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://graph.microsoft.com/beta/me/presence","tls":"","persist":false,"proxy":"","authType":"","x":490,"y":580,"wires":[["cd2de8e1.fdbdd8"]]},{"id":"4a82e18f.d26e","type":"inject","z":"7c76e545.92af9c","name":"","props":[{"p":"payload"}],"repeat":"5","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":580,"wires":[["f74a2254.e0487"]]},{"id":"f74a2254.e0487","type":"function","z":"7c76e545.92af9c","name":"","func":"var access_token = flow.get('access_token'); \n\nif(!access_token)\n{\n node.status({fill:\"blue\",shape:\"dot\",text:`Access token missing. Exiting`});\n return null;\n}\n\nmsg.headers= {\n \"Authorization\": \"Bearer \"+access_token\n };\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":320,"y":580,"wires":[["97a36414.d2b318"]]},{"id":"cd2de8e1.fdbdd8","type":"function","z":"7c76e545.92af9c","name":"","func":"var response = msg.payload;\nif(response.hasOwnProperty('availability') && response.hasOwnProperty('activity'))\n{\n node.status({fill:\"green\",shape:\"dot\",text:`Status: ${response.availability} (${response.activity})`});\n return [ { \"payload\": {\n \"availability\": response.availability,\n \"activity\": response.activity\n }}]; \n}\nnode.status({fill:\"red\",shape:\"ring\",text:`Status: some error occurred`});\n\nconsole.log(\"no property availability\");","outputs":1,"noerr":0,"initialize":"","finalize":"","x":700,"y":580,"wires":[[]]},{"id":"8e2a7b69.0d2f38","type":"comment","z":"7c76e545.92af9c","name":"Available Presence Properties","info":"[Docs](https://docs.microsoft.com/en-us/graph/api/resources/presence?view=graph-rest-beta#properties)","x":770,"y":520,"wires":[]},{"id":"b28b4494.18e868","type":"inject","z":"7c76e545.92af9c","name":"change my values","props":[{"p":"scope","v":"Presence.Read offline_access","vt":"str"},{"p":"tenant_id","v":"","vt":"str"},{"p":"client_id","v":"","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payloadType":"str","x":130,"y":40,"wires":[["4bb4827.9197c7c"]]},{"id":"4bb4827.9197c7c","type":"function","z":"7c76e545.92af9c","name":"prepare context","func":"flow.get('tenant_id', function(err, tenant_id) {\n if (err) {\n node.error(err, msg);\n } else {\n // store the value\n flow.set('tenant_id',msg.tenant_id, function(err) {\n if (err) {\n node.error(err, msg);\n } else {\n flow.get('scope', function(err, scope) {\n if (err) {\n node.error(err, msg);\n } else {\n // store the value\n flow.set('scope',msg.scope, function(err) {\n if (err) {\n node.error(err, msg);\n } else {\n flow.get('client_id', function(err, client_id) {\n if (err) {\n node.error(err, msg);\n } else {\n // store the value\n flow.set('client_id',msg.client_id, function(err) {\n if (err) {\n node.error(err, msg);\n } \n // no else here\n });\n }\n });\n }\n });\n }\n });\n node.status({fill:\"green\",shape:\"dot\",text:`OK: context prepared`});\n }\n });\n }\n});","outputs":1,"noerr":0,"initialize":"","finalize":"","x":360,"y":40,"wires":[[]]},{"id":"427185e9.4132fc","type":"function","z":"7c76e545.92af9c","name":"prepare device code request","func":"msg.headers = { \"Content-Type\": \"application/x-www-form-urlencoded\"};\n\nvar context = flow.get(['tenant_id','client_id','scope']);\nvar tenant_id = context[0];\nvar client_id = context[1];\nvar scope = context[2];\nif(tenant_id && client_id && scope)\n{\n msg.url = \"https://login.microsoftonline.com/\"+tenant_id+\"/oauth2/v2.0/devicecode\"\n msg.payload = { \n \"client_id\": client_id,\n \"scope\": scope\n };\n node.status({fill:\"green\",shape:\"dot\",text:`Values passed on`});\n return msg;\n}\n\nnode.status({fill:\"red\",shape:\"dot\",text:`ERROR: context not prepared`});","outputs":1,"noerr":0,"initialize":"","finalize":"","x":460,"y":160,"wires":[["40792ca0.843c24"]]},{"id":"ddaa0b36.e42108","type":"delay","z":"7c76e545.92af9c","name":"","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":520,"y":240,"wires":[["648d5fe3.74d0b"]]},{"id":"d32c769a.1c42b8","type":"function","z":"7c76e545.92af9c","name":"","func":"var context = flow.get(['access_token','refresh_token']);\nvar access_token = context[0]; \nvar refresh_token = context[1]; \n\nif(msg.payload.hasOwnProperty('access_token') && \nmsg.payload.hasOwnProperty('refresh_token'))\n{\n flow.set('device_code',undefined);\n node.status({fill:\"green\",shape:\"dot\",text:`device now logged in, pass on message`});\n return [null, msg]; \n}\n\n\nif(access_token && refresh_token)\n{\n flow.set('device_code',undefined);\n node.status({fill:\"green\",shape:\"dot\",text:`device already logged in`});\n return [];\n}\n\nif(msg.payload.hasOwnProperty('error'))\n{\n if(msg.payload.error == \"authorization_pending\")\n {\n node.status({fill:\"blue\",shape:\"dot\",text:`Browser login pending`});\n msg.delay = 5*1000;\n return [msg, null]; \n }\n node.status({fill:\"red\",shape:\"dot\",text:`Error: ${msg.payload.error_description}`});\n return [];\n}\n","outputs":2,"noerr":0,"initialize":"","finalize":"","x":740,"y":320,"wires":[["ddaa0b36.e42108"],["d1253feb.c9362","657e48c9.bcda48"]]},{"id":"643ed329.5bdfec","type":"debug","z":"7c76e545.92af9c","name":"Auth link and device code","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":980,"y":100,"wires":[]},{"id":"2347386d.7321d8","type":"comment","z":"7c76e545.92af9c","name":"MS Graph request (presence)","info":"[Docs](https://docs.microsoft.com/en-us/graph/api/resources/presence?view=graph-rest-beta#properties)","x":190,"y":520,"wires":[]}]
这为您提供了以下流程,该流程能够使用 node-red读取您的Microsoft Teams 在线状态/ Microsoft Office 365 在线状态: