- 
                Notifications
    
You must be signed in to change notification settings  - Fork 1
 
Add route for impersonating users from association id #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -9,3 +9,4 @@ yarn-error.log | |
| npm-error.log | ||
| /.vscode | ||
| /.idea | ||
| .env | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| const fetchPromise = import("node-fetch"); | ||
| 
     | 
||
| async function getSaToken() { | ||
| const fetch = (await fetchPromise).default; | ||
| 
     | 
||
| const resp = await fetch( | ||
| "https://sso.csh.rit.edu/auth/realms/master/protocol/openid-connect/token", | ||
| { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/x-www-form-urlencoded", | ||
| Authorization: `Basic ${Buffer.from( | ||
| process.env.GK_SA_USERNAME + ":" + process.env.GK_SA_PASSWORD | ||
| ).toString("base64")}`, | ||
| }, | ||
| body: "grant_type=client_credentials", | ||
| } | ||
| ); | ||
| const json = await resp.json(); | ||
| 
     | 
||
| return json["access_token"]; | ||
| } | ||
| 
     | 
||
| async function getUidFromUsername(username, saToken) { | ||
| const fetch = (await fetchPromise).default; | ||
| 
     | 
||
| const resp = await fetch( | ||
| "https://sso.csh.rit.edu/auth/admin/realms/csh/users?username=" + username, | ||
| { | ||
| headers: { | ||
| Authorization: `Bearer ${saToken}`, | ||
| }, | ||
| } | ||
| ); | ||
| const json = await resp.json(); | ||
| 
     | 
||
| return json[0]["id"]; | ||
| } | ||
| 
     | 
||
| async function getImpersonationSession(userId, saToken) { | ||
| const fetch = (await fetchPromise).default; | ||
| 
     | 
||
| const resp = await fetch( | ||
| `https://sso.csh.rit.edu/auth/admin/realms/csh/users/${userId}/impersonation`, | ||
| { | ||
| method: "POST", | ||
| headers: { | ||
| Authorization: `Bearer ${saToken}`, | ||
| }, | ||
| } | ||
| ); | ||
| const headers = resp.headers; | ||
| const cookies = headers.get("set-cookie"); | ||
| 
     | 
||
| const identityRegex = /KEYCLOAK_IDENTITY=\S+/; | ||
| const sessionRegex = /KEYCLOAK_SESSION=\S+/; | ||
| 
     | 
||
| const identity = identityRegex | ||
| .exec(cookies)[0] | ||
| .split("=")[1] | ||
| .replace(";", ""); | ||
| const session = sessionRegex.exec(cookies)[0].split("=")[1].replace(";", ""); | ||
| 
     | 
||
| return {identity, session}; | ||
| } | ||
| 
     | 
||
| async function getUserToken(identity, session) { | ||
| const fetch = (await fetchPromise).default; | ||
| 
     | 
||
| const resp = await fetch( | ||
| "https://sso.csh.rit.edu/auth/realms/csh/protocol/openid-connect/auth?client_id=gatekeeper&response_type=token&response_mode=fragment&redirect_uri=https%3A%2F%2Fgatekeeper.csh.rit.edu%2Fcallback", | ||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be cool if this didn't always use the same client ID (think: audiophiler)  | 
||
| { | ||
| method: "GET", | ||
| headers: { | ||
| "Content-Type": "application/x-www-form-urlencoded", | ||
| Cookie: `KEYCLOAK_SESSION=${session}; KEYCLOAK_IDENTITY=${identity};`, | ||
| }, | ||
| redirect: "manual", | ||
| } | ||
| ); | ||
| 
     | 
||
| const headers = resp.headers; | ||
| const location = new URL(headers.get("location").replace("#", "?")); | ||
| 
     | 
||
| let accessToken = location.searchParams.get("access_token"); | ||
| 
     | 
||
| return accessToken; | ||
| } | ||
| 
     | 
||
| async function getImpersonationToken(userId) { | ||
| const saToken = await getSaToken(); | ||
| const uid = await getUidFromUsername(userId, saToken); | ||
| const {identity, session} = await getImpersonationSession(uid, saToken); | ||
| const accessToken = await getUserToken(identity, session); | ||
| return {uid, userId, accessToken}; | ||
| } | ||
| 
     | 
||
| module.exports = { | ||
| getImpersonationToken, | ||
| }; | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| const router = require("express").Router(); | ||
| const ldap = require("../ldap"); | ||
| const impersonate = require("../impersonate"); | ||
| 
     | 
||
| function findUser(id) { | ||
| return new Promise((resolve, reject) => { | ||
| 
          
            
          
           | 
    @@ -88,4 +89,53 @@ router.get("/by-key/:associationId", async (req, res) => { | |
| }); | ||
| }); | ||
| 
     | 
||
| router.get("/impersonate/:associationId", async (req, res) => { | ||
| const key = await req.ctx.db.collection("keys").findOne({ | ||
| [req.associationType]: {$eq: req.params.associationId}, | ||
| }); | ||
| 
     | 
||
| if (!key) { | ||
| res.status(404).json({message: "Not found"}); | ||
| return; | ||
| } | ||
| 
     | 
||
| const userDocument = await req.ctx.db.collection("users").findOne({ | ||
| id: {$eq: key.userId}, | ||
| disabled: {$ne: true}, | ||
| }); | ||
| if (!userDocument) { | ||
| res.status(404).json({message: "User not found or disabled"}); | ||
| return; | ||
| } | ||
| 
         
      Comment on lines
    
      +102
     to 
      +109
    
   
  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary query to DB  | 
||
| 
     | 
||
| let user; | ||
| try { | ||
| user = await findUser(key.userId); | ||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This pulls every attribute, which will be MUCH slower than just fetching attributes we want. Look at  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps add an optional   | 
||
| } catch (err) { | ||
| res.status(500).json({message: "Internal server error"}); | ||
| return; | ||
| } | ||
| 
     | 
||
| const response = {}; | ||
| for (const attribute of user.attributes) { | ||
| if (attribute.type == "jpegPhoto") { | ||
| response[attribute.type] = attribute._vals[0].toString("base64"); | ||
| } else { | ||
| const values = attribute._vals.map((value) => value.toString("utf8")); | ||
| if (ARRAYS.has(attribute.type)) { | ||
| response[attribute.type] = values; | ||
| } else { | ||
| if (values.length > 1) { | ||
| console.warn(`${attribute.type} has many values!!`); | ||
| } | ||
| response[attribute.type] = values.join(","); | ||
| } | ||
| } | ||
| } | ||
| 
     | 
||
| const uid = response["uid"]; | ||
| 
         
      Comment on lines
    
      +119
     to 
      +136
    
   
  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having my jetbrains arc 💡 Loop can be simplified: const uid = user.attributes.find(attribute => attribute.type == "uid")._vals[0].toString("utf8");We should really use a different client for drink vs normal... One should grant read/write drink credits scope to  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Disappointed there's no way to search by   | 
||
| 
     | 
||
| res.json(await impersonate.getImpersonationToken(uid)); | ||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice if we added   | 
||
| }); | ||
| 
     | 
||
| module.exports = router; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/.env