deft/notes/cisco_ft_securex_registration.org
2022-01-17 15:25:15 +01:00

14 KiB

Cisco FT SecureX Simplified Registration

tags
Auth
source
https://github.com/advthreat/response/issues/821
dashboard
https://github.com/advthreat/iroh/projects/32

Technical Plan

DONE 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.

  • State "CANCELED" from "HOLD" [2022-01-17 Mon 10:57]
    This only concern new account creation. User with gmail account could still login.

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.

  1. Search via text match.

The algorithm should look a bit like:

;; 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))

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.

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 CRUD Services

OrgAccessRequest

This new service should be mostly a CRUD service on top of TKStore with the following schema:

(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
     })))
(defprotocol OrgAccessRequestService
  "See iroh-auth.registration.org-access-request.schemas/ServiceFns for schemas."
  :extend-via-metadata true

  (grant-org-access-request-authorization!
    [this request-identity org-access-request-id]
    "The request-identity should be the one of the admin of the org
     for which the access request has been made.
     This should grant the access if all check passes.")

  ;; User filtered CRUD+Search for REST API related methods
  (search-org-access-requests-for-user-identity
    [this user-identity filter-map pagination-params]
    "Search all OrgAccessRequest made by this user identity accross all orgs.
     This should only be visible via the registration page on the new API accepting
     user-identity JWT")

  ;; User filtered CRUD+Search for REST API related methods
  (search-org-access-requests-for-org
    [this request-identity filter-map pagination-params]
    "Search all OrgAccessRequest of the org of the request-identity")

  (get-org-access-request
    [this request-identity org-access-request-id]
    "Return the refresh token for a user using the org-access-request-id")

  (patch-org-access-request
    [this request-identity org-access-request-id org-access-request-patch]
    "Change the list of scopes associated to a refresh token on the server-side
     Could also be used to enable/disable a refresh token grant.")

  ;; Internal CRUD+Search
  (raw-search-org-access-requests
    [this filter-map pagination-params]
    "Search all refresh token grants")

  (raw-get-org-access-request
    [this org-access-request-id]
    "Return the refresh token grant")

  (raw-patch-org-access-request
    [this org-access-request-id org-access-request-patch]
    "Change the list of scopes associated to a refresh token on the server-side
     Could also be used to enable/disable a refresh token grant."))
UserIdentity

Should look a lot like a user but instead of a user-id it should have a user-identity-id and should not have any org-id value.

It should also contain a field containing a set of hidden org-ids that will be the orgs not to display on the registration webpages.

(s/defschema UserIdentity
  (ist/open-schema-any-keys
   {:id s/Str
    :email
    :name
    :nickname
    :last-logged-in [,,,]
    }))

New IROH-Auth API

Estimate: 3 release cycle

This new API should only work with UserIdentity JWT.

POST /iroh/registration/org-access/:org-id ;; request access to a matching org
POST /iroh/registration/hide-org/:org-id ;; ability to hide an org-access
GET  /iroh/registration/matching-accounts ;; list all the matching accounts
GET  /iroh/registration/matching-orgs ;; list all the matching orgs
GET  /iroh/registration/pending-invites ;; list all the pending invites
GET  /iroh/registration/registration-view ;; helper to make a single http call
Request Org Access

We need to create another store with another Entity for access request to an Org.

(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
     })))

When a user request access to an organization. We should create this object in DB.

POST  /iroh/registration/org-access/:org-id ;; request access to a matching org
POST /iroh/registration/org-access/:org-id

Accept: application/json
Content-Type: application/json
User-Agent: ob-http
Authorization: Bearer ${user-identity-jwt}

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 the /yogsototh/deft/src/commit/caacf1dc2b302360525dc277424e721374eb7b7d/notes/%2AEmail%20Notification%20of%20Org%20Request%20Accesses section)
Hide Org
POST /iroh/registration/hide-org/:org-id

Accept: application/json
Content-Type: application/json
User-Agent: ob-http
Authorization: Bearer ${user-identity-jwt}

Should save in the user identity store a new org-id to hide. This impacts the call to `matching-orgs` such that orgs marked as hidden should not be displayed when calling the `matching-org` route.

List Matching Accounts
GET  /iroh/registration/matching-accounts

Accept: application/json
Content-Type: application/json
User-Agent: ob-http
Authorization: Bearer ${user-identity-jwt}

It should return a list of object with the following schema sorted by last logged in time by the user:

{:user User
 :org Org
 :last-login-date DateTime
 :relative-last-login s/Str
 :org-created-at DateTime
 :relative-org-created-at s/Str
 :activated? s/Bool
 :org-nb-users s/Int}
List Matching Orgs
GET  /iroh/registration/matching-orgs

Should return a list of objects with the following schema (sort to be defined):

{:org Org
 :org-nb-users s/Int}
List Pending Invites
GET  /iroh/registration/pending-invites

Here is an example value:

{:role "admin",
 :org-id "org-1",
 :expires-in-nb-days 7,
 :status "pending",
 :invitee-email "chuck@example.org",
 :inviter-user-id "org-1-admin-1"}
Registration View

GET /iroh/registration/registration-view

Should return the same result as the union of the calls to matching-accounts, matching-orgs and pending-invites.

{:matching-accounts [,,,]
 :matching-orgs [,,,]
 :pending-invites [,,,]}

New IROH-Auth login process.

Every-time a users successfully login via an IdP we should synchronize the UserIdentity value in our DB accordingly. It should occurs not only during the account registration, but also for every new login.

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/<id> read a single org access request
  • POST /iroh/user-mgmt/org-requests/<id>/accept Grant the access
  • POST /iroh/user-mgmt/org-requests/<id>/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/<id>/accept Grant the access

The body should contain the role (either admin or user) with the following schema:

{"role":"admin"}

During the call, should:

  1. Create a new user with:
{: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
 }
  1. 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/<id>/reject Reject the access

This call should update the OrgAccessRequest object by patching with:

{:udpated-at (now)
 :approver-id (get-in req [:identity :user :id])
 :approver-email (get-in req [:identity :user :email])
 :status :rejected}

Then send an email to user confirming his access was denied.

TODO have an email template + text.