微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

没有ROPC的Node-Red中的Microsoft Identity Platform

如何解决没有ROPC的Node-Red中的Microsoft Identity Platform

是否可以在不使用Node-Red的ROPC身份验证流的情况下,将Microsoft Identiy平台与OAuth 2.0流一起使用?我不能使用ROPC,因为目标租户强制执行MFA。 ROPC will be blocked when MFA is enforced

我找到了插件node-red-contrib-oauth2,但无法通过ROPC以外的其他OAuth 2.0流程与Microsoft Identity Platform一起使用。

解决方法

解决方案是使用Device Code Flow。以下说明为您提供了一个流程,该流程可以读取带有节点红色的 Microsoft Teams状态 / Microsoft Office 365状态

在Azure门户中创建应用

在以下示例中,我们将创建一个应用程序,该应用程序只能读取已登录用户的状态。这意味着,API权限可能会根据您的需求而有所不同。

  1. 登录到Azure门户并转到Azure Active Directory,然后转到“应用程序注册”并单击“ +新注册”
  2. 输入一个易于识别的友好名称。
  3. 受支持的帐户:仅此组织目录中的帐户(*-单租户)
  4. 重定向URI:留空,我们稍后将对此进行设置。
  5. 点击“注册”
  6. 转到“ API权限”。
  7. 删除默认权限,因为该权限并不需要。
  8. 点击“ +添加权限”。
  9. 单击“ Microsoft Graph”,然后单击“授权”。
  10. 选择“ offline_access”和“ Presence.Read”,然后将其保存为“添加权限”

说明:


  1. 然后,您必须通过单击“ +添加许可按钮”旁边的“授予*的管理员许可”按钮来“授予管理员许可”。
  2. 导航回“概述”并复制“应用程序(客户端)ID”,“目录(租户)ID”的值。
  3. 在导航抽屉中选择“身份验证”。
  4. 点击“ +添加平台”
  5. 选择“移动和桌面应用程序”
  6. 选择“ https://login.microsoftonline.com/common/oauth2/nativeclient”
  7. 提交“配置”
  8. 向下滚动到“高级设置”,然后在“将应用程序作为公共客户端处理”旁边选择“是”。并在左上角提交“保存”。

现在该应用程序可以在Node-Red中使用,以从MS Graph API中读取状态信息。

在Node-Red中使用此应用

这种流程的一个很好的起点是:

[{"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","name":"launch device code request","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"wires":[["427185e9.4132fc"]]},{"id":"657e48c9.bcda48","type":"function","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","name":"Set access_token","func":"flow.get('access_token',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,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\",text:`access_token: ${msg.access_token}`});\n                node.send(msg);\n            }\n        });\n    }\n});\n","y":300,{"id":"44c485e1.fa8d2c","name":"Set device_code","func":"flow.get('device_code',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,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\",text:`device_code: ${msg.device_code}`});\n                node.send(msg);\n            }\n        });\n    }\n});\n","x":950,{"id":"648d5fe3.74d0b","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\",text:`device_code: ${device_code.substring(0,10)}`});\n    return [null,msg];\n}\n","outputs":2,"x":320,"y":320,"wires":[["ddaa0b36.e42108"],["1fc0a330.6fe22d"]]},{"id":"ec8a6999.9abcd8","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"x":130,"wires":[["648d5fe3.74d0b"]]},{"id":"1fc0a330.6fe22d","x":510,"wires":[["d32c769a.1c42b8"]]},{"id":"ba3c9093.320a7","type":"comment","name":"Retrieve tokens ...","info":"... after login has been made in a browser","y":260,"wires":[]},{"id":"d4b1dae2.c6c538","name":"refresh tokens every 30 minutes","info":"","x":170,"y":400,{"id":"396bdeb0.93db72","repeat":"1800","y":440,"wires":[["8739bad6.405738"]]},{"id":"8739bad6.405738","name":"refresh request",'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","x":340,"wires":[["1aafbb4a.e050b5"]]},{"id":"1aafbb4a.e050b5","x":550,{"id":"97a36414.d2b318","method":"GET","url":"https://graph.microsoft.com/beta/me/presence","x":490,"y":580,"wires":[["cd2de8e1.fdbdd8"]]},{"id":"4a82e18f.d26e","repeat":"5","x":160,"wires":[["f74a2254.e0487"]]},{"id":"f74a2254.e0487","func":"var access_token = flow.get('access_token'); \n\nif(!access_token)\n{\n    node.status({fill:\"blue\",text:`Access token missing. Exiting`});\n    return null;\n}\n\nmsg.headers= {\n            \"Authorization\": \"Bearer \"+access_token\n        };\nreturn msg;","wires":[["97a36414.d2b318"]]},{"id":"cd2de8e1.fdbdd8","func":"var response = msg.payload;\nif(response.hasOwnProperty('availability') && response.hasOwnProperty('activity'))\n{\n    node.status({fill:\"green\",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\");","x":700,{"id":"8e2a7b69.0d2f38","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,{"id":"b28b4494.18e868","name":"change my values","props":[{"p":"scope","v":"Presence.Read offline_access","vt":"str"},{"p":"tenant_id","v":"",{"p":"client_id","once":true,"payloadType":"str","y":40,"wires":[["4bb4827.9197c7c"]]},{"id":"4bb4827.9197c7c","name":"prepare context","func":"flow.get('tenant_id',tenant_id) {\n    if (err) {\n        node.error(err,msg);\n    } else {\n        // store the value\n        flow.set('tenant_id',msg.tenant_id,msg);\n            } else {\n                flow.get('scope',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',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\",text:`OK: context prepared`});\n            }\n        });\n    }\n});","x":360,{"id":"427185e9.4132fc","name":"prepare device code request","func":"msg.headers = { \"Content-Type\": \"application/x-www-form-urlencoded\"};\n\nvar context = flow.get(['tenant_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\",text:`Values passed on`});\n    return msg;\n}\n\nnode.status({fill:\"red\",text:`ERROR: context not prepared`});","x":460,"wires":[["40792ca0.843c24"]]},{"id":"ddaa0b36.e42108","type":"delay","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":520,"y":240,{"id":"d32c769a.1c42b8","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\",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',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\",text:`Browser login pending`});\n        msg.delay = 5*1000;\n        return [msg,null]; \n    }\n    node.status({fill:\"red\",text:`Error: ${msg.payload.error_description}`});\n    return [];\n}\n","x":740,["d1253feb.c9362","657e48c9.bcda48"]]},{"id":"643ed329.5bdfec","type":"debug","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,{"id":"2347386d.7321d8","name":"MS Graph request (presence)","x":190,"wires":[]}]

这为您提供了以下流程,该流程可以读取带有节点红色的 Microsoft Teams状态 / Microsoft Office 365状态

flow showcase

  1. 双击“更改我的值”节点,然后输入client_id,tenant_id和使用的范围。已经设置了用于读取状态信息的范围默认值。
  2. 部署节点。
  3. 启动“启动设备代码请求”。
  4. 在调试控制台中,您将获得一个代码,应复制该代码并在任何设备上的浏览器中打开给定的链接。
  5. 在浏览器中进行身份验证过程,直到显示登录成功并可以关闭浏览器窗口为止。
  6. 然后单击“获取令牌”下的注入节点。
  7. 如果右边的节点显示绿色值,则表示一切成功,并且您准备就绪。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。