:PROPERTIES: :ID: 1208f09c-d37d-4e6b-9110-151f3c6b7d34 :END: #+TITLE: Cisco FT SecureX Simplified Registration #+Author: Yann Esposito #+Date: [2021-12-07] - tags :: [[id:299643a7-00e5-47fb-a987-3b9278e89da3][Auth]] - source :: https://github.com/advthreat/response/issues/821 - dashboard :: https://github.com/advthreat/iroh/projects/32 * Functional Spec Response issues: - https://github.com/advthreat/response/issues/821 Figma: https://www.figma.com/file/Bz3m25kpWXpdct7AnhmNsW/SXSO-Registeration?node-id=759%3A5926 * Technical Plan ** Support private email vs public emails *DONE* +/Estimate: 1 release cycles after the list is provided./+ The solution is to use a blacklist of domains where any user could create multiple email accounts pseudo-anonymously. Details: https://github.com/advthreat/response/issues/979 *** CANCELED Support allow-list exceptions for some Cisco user. :LOGBOOK: - State "CANCELED" from "HOLD" [2022-01-17 Mon 10:57] \\ This only concern new account creation. User with gmail account could still login. :END: /Estimate: 1 release cycles after the list is provided./ Typically we should allow some users with an email like =some-user+XXX@gmail.com= ** Support, search admin with same email domain /Estimate: 1 release cycle./ Note: *Work in progress* We should be able given an email from a user, to find all the orgs for which at least one of its admin has a matching domain name. 1. Most efficient: add an invisible field =email-domain= to all users. This should be lower-case, and we will need a migration. Doing this we could have a faster match than using string related queries. Problems, users can login in the same user, with the same public email with different emails. This should be rare. 2. Search via text match. The algorithm should look a bit like: #+begin_src clojure ;; only when this is an unknown user ;; so a single approval will prevent the user to see this page. (let [user-email ,,, domain (string/replace user-email #".*@" "") users (matching-admins domain) ;; returns a potentially big list of admin users indexed-orgs (group-by :org-id users)] (vals indexed-orgs)) #+end_src Once this list of orgs is found. We should also check the list of pending or rejected OrgAccessRequest for this user in order to prevent the user to request access multiple time. ** Support Org request to admins /Estimate: 4 release cycles/ We need to create another Entity for access request to an Org. #+begin_src clojure (s/defschema OrgAccessRequest (st/merge {:id UUID :idp-mapping IdPMapping :user-email s/Str :org-id s/Str :status (s/enum :pending :accepted :rejected) :created-at DateTime} (st/optional-keys {:user-name s/Str :user-nick s/Str :approver-id UserId :approver-email UserEmail ;; email of the approver :updated-at DateTime :hidden? s/Bool ;; if the user want to hide this matching org }))) #+end_src When a user request access to an organization. We should create this object in DB. ** New UI Sync /Estimate: 5 release cycles/ We should find a way to hand the UI work to the UI team. Right now, the page are all generated in IROH. To reach that ideally we should sync the source code as a jar in IROH. In order to give the UI the ability to make a front-end application, we should create news APIS that support a new UserIdentity-level JWT *** New Auth APIs Continue to use the =/code= route of iroh-auth. But instead of returning a Session Token, it will return a UserIdentity Token. This is a JWT with the important data from the IdP: - idp-id - user-identity-id - user-email - etc… When the user is redirected to an HTML generated page, we should add the =code= in the anchor parameter of the route so the UI will be able to use that code to retrieve a UserIdentity Token. *** New API JWT middleware /Estimate: 1 release cycle/ We need to change the configuration of the check JWT middleware to support UserIdentity Token instead. And use this configuration for this new API. The =user-identity-jwt= should contain enough data to retrieve: - =idp-mapping= - =user-email= - all other metas, as user-name, user-nick, etc… *** New IROH-Auth API This new API should only work with UserIdentity JWT. #+begin_src POST /iroh/registration/org-access/:org-id ;; request access to a matching org PATCH /iroh/registration/org-access/:org-id ;; ability to hide an org-access GET /iroh/registration/matching-orgs ;; list all the matching orgs (should hide org with org-access hidden) #+end_src **** Request Org Access #+begin_src POST /iroh/registration/org-access/:org-id ;; request access to a matching org #+end_src #+begin_src http POST /iroh/registration/org-access/:org-id Accept: application/json Content-Type: application/json User-Agent: ob-http Authorization: Bearer ${user-identity-jwt} #+end_src With this, and if every check matches: 1. There is a known active admin of this org with an email with the same domain name 2. The org is active We should: 1. create a new =OrgAccessRequest= object in DB. 2. Send emails (see next section) *** Login Account Management/Matching Org Page #+begin_quote On the 2021-01-13 the login account management page was decided to be changed for just a matching org page. This does not change fundamentally the amount of work to be done. #+end_quote See Figma: https://www.figma.com/file/Bz3m25kpWXpdct7AnhmNsW/SXSO-Registeration?node-id=759%3A5926 We should change the login flow, so the user might see an intermediate /login account management page/. On this /login account management page/ the user should see: 1. account selection 2. pending invites 3. matching orgs To give an idea here is an example of what it will look like: #+begin_src Select Account: - account-1 - account-2 Pending Invites: - invite for org-1 - invite for org-2 (expired) [ask for renewal] Matching Orgs: - org-4 - org-5 #+end_src **** Behavior If the user is only part of 1 org, and has no invite, and not matching org. He should not see that page and be redirected directly to his account. If the user is only part of 1 org, and has matching org, it is not clear if we should display that page? If all other cases, the "login account management page" Note also that if we have a pending invite on a matching email domain org. The pending invite should take the priority when displaying the org. ***** Question 1. pending invite expired 2. matching org We should present the user: - request access to the org ** Email Notification of Org Request Accesses /Estimate: 4 release cycle after email+html templates/ 1. List all the admins of the requested org. 2.a. If there is fewer admins than a number that could be configured in the node configuration. Then we send an email to all admins. 2.b. If there are more admins than this specific number, then we randomly chose this maximal number of admins and send them an email notification. *TODO* Have an email template (both HTML and Text) *** Notes Need to be able to trigger a new request to join after 7 days. ** Org Requests CRUD API for Admins of the Orgs /Estimate: 3 release cycle after mail templates + text/ There should be a CRUD API restricted to the ~admin/user-mgmt/org-requests~ scope: - ~GET /iroh/user-mgmt/org-requests~ list pending org access requests - ~GET /iroh/user-mgmt/org-requests/~ read a single org access request - ~POST /iroh/user-mgmt/org-requests//accept~ Grant the access - ~POST /iroh/user-mgmt/org-requests//reject~ Reject the access *** List ~GET /iroh/user-mgmt/org-requests~ If no parameter is provided, only list pending =OrgAccessRequests= of the org of the caller. Otherwise we could pass the query-parameter =status= with the following value(s): - =pending= - =accepted= - =rejected= Note we should probably support duplicate statuses. Ex: ~GET /iroh/user-mgmt/org-requests?status=accepted&status=pending~ *** Read ~GET /iroh/user-mgmt/org-requests/org-request-id~ Should returns a 404 if not found or the single Org Access Request object. *** Accept the Org Access ~POST /iroh/user-mgmt/org-requests//accept~ Grant the access The body should contain the role (either =admin= or =user=) with the following schema: #+begin_src js {"role":"admin"} #+end_src During the call, should: 1. Create a new user with: #+begin_src clojure {:user-id (gen-uuid) :org-id (:org-id org-access-request) :user-email (:user-email org-access-request) :idp-mappings [(:idp-mapping org-access-request)] :user-name (:user-name org-access-request) :user-nick (:user-nick org-access-request) :role (get-in request [:body :role]) :enabled? true } #+end_src 2. Send an email to user confirming his access was granted. *TODO* have an email template + text. *** Reject the Org Access ~POST /iroh/user-mgmt/org-requests//reject~ Reject the access This call should update the =OrgAccessRequest= object by patching with: #+begin_src clojure {:udpated-at (now) :approver-id (get-in req [:identity :user :id]) :approver-email (get-in req [:identity :user :email]) :status :rejected} #+end_src Then send an email to user confirming his access was denied. *TODO* have an email template + text.