# Design ## Current ### Architecture ```plaintext ------------- | user-agent | ------------- | V ----|:443|------------------------------ | V------------| | | |------| | V V V | ---------------------------- |--->| authn | CI | ... | authz | V |--------------------------| |--->| UI | REST API | ... | | ---------------------------- V A | | |---->---->--- ``` ## Proposed ### Architecture ```plaintext ------------- | user-agent | ------------- | V ----|:8080|----------------------------------------------- | V --------------- | API Gateway | --------------- | | --------------------------------- | | IdP (saml, oidc) | | |-------------------------------| |--->| :http (authn) | :grpc (authz) | | --------------------------------- | A ----------- | | | | V V | ------ ------------ | | UI | | REST API |----------| ------ ------------ | | A |---->----------->------------| [UI]: ui.example.com [REST API]: api.example.com [IdP]: idp.example.com ``` SAML Login Flow ```plantuml @startuml Browser -> UI: 1. Get dashboard UI --> Browser: Generate SAML and redirect to IdP Browser -> IdP: 2. Deliver SAML IdP --> Browser: 3. Redirect to Login Page Browser -> IdP: 4. Login IdP --> Browser: 5. Generate SAML with and redirect to UI Browser -> UI: 6. Deliver SAML UI -> IdP: 7. Exchange for Tokens IdP --> UI: Return `access_token` and `refresh_token` UI --> Browser: Redirect to dashboard Browser -> UI: Get dashboard UI -> API: 8. Request list of groups and provide Access Token API -> IdP: 9. Check if token is valid and check declarative policy IdP --> API: Return result of `Ability.allowed?` API --> UI: Return list of groups as JSON UI --> Browser: Return list of groups as HTML @enduml ``` 1. `GET http://ui.example.com/saml/new` 2. `POST http://idp.example.com/saml/new` 3. `GET http://idp.example.com/sessions/new?redirect_back=/saml/continue` 4. `POST http://idp.example.com/sessions` 5. `GET http://idp.example.com/saml/continue` 6. `POST http://ui.example.com/saml/assertions` 7. `POST http://idp.example.com/oauth/token` 8. `GET http://api.example.com/groups.json` 9. `GET grpc://idp.example.com/twirp/authx.rpc.Ability/Allowed` OIDC Login Flow ```plantuml @startuml Browser -> UI: 1. Get dashboard UI --> Browser: Generate OAuth Grant Request and redirect to IdP Browser -> IdP: 2. Deliver OAuth Grant Request IdP --> Browser: 3. Redirect to Login Page Browser -> IdP: 4. Login IdP --> Browser: 5. Generate Consent Screen for Authorization Code flow Browser -> IdP: 6. Consent IdP --> Browser: Generate Authorization Code and redirect to UI Browser -> UI: 7. Deliver Authorization Code Grant UI -> IdP: 8. Exchange Authorization Code Grant for Tokens IdP --> UI: Return `access_token` and `refresh_token` UI --> Browser: Redirect to dashboard Browser -> UI: Get dashboard UI -> API: 9. Request list of groups and provide Access Token API -> IdP: 10. Check if token is valid and check declarative policy IdP --> API: Return result of `Ability.allowed?` API --> UI: Return list of groups as JSON UI --> Browser: Return list of groups as HTML @enduml ``` 1. `GET http://ui.example.com/oidc/new` 2. `GET http://idp.example.com/oauth/authorize` 3. `GET http://idp.example.com/sessions/new?redirect_back=/oauth/authorize/continue` 4. `POST http://idp.example.com/sessions` 5. `GET http://idp.example.com/oauth/authorize/continue` 6. `POST http://idp.example.com/oauth/authorize` 7. `GET http://ui.example.com/oauth/callback` 8. `POST http://idp.example.com/oauth/token` 9. `GET http://api.example.com/groups.json` 10. `GET grpc://idp.example.com/twirp/authx.rpc.Ability/Allowed` ### Permissions #### Option 1 | permission | scope | description | | ---------- | ----- | ----------- | | `read` | `gid://app/Organization/1` | Can read Org 1 resource | | `read` | `gid://app/Organization/1/*` | Can read every resource below Org 1 hierarchy | | `read` | `gid://app/Organization/1/Group/1` | Can read Group 1 resource | | `read` | `gid://app/Organization/1/Group/1/*` | Can read every resource below Group 1 hierarchy | | `read` | `gid://app/Organization/1/Group/1/Project/1` | Can read project 1 | | `read` | `gid://app/Project/1` | Can read project 1 resource (short circuit example) | | `read` | `gid://app/Organization/1/Group/1?attributes[]=name&attributes[]=description` | Can read name and description of Group 1 resource | Example: The following example allows the subject of the token to read all of the descendant resources of `Project 1` and `Project 2` and it can read `Project 3`. ```json { "sub": "gid://User/17", "scope": { "read": [ "gid://app/Organization/1/Group/1/Project/1/*", "gid://app/Organization/1/Group/1/Project/2/*", "gid://app/Organization/1/Group/2/Project/3" ] } } ``` #### Option 2 Encode access and scope directly into the name of the permission. | permission | description | | ---------- | ----------- | | `read:organization:1` | Can read Org 1 resource | | `read:organization:1:*` | Can read every resource below Org 1 hierarchy | | `read:organization:1:group:*` | Can read Group 1 resource | | `read:organization:1:group:1:*` | Can read every resource below Group 1 hierarchy | | `read:organization:1:group:1:project:1` | Can read project 1 | | `read:project:1` | Can read project 1 resource (short circuit example) | | `read:organization:1:group:1:attributes[]=name&attributes[]=description` | Can read name and description of Group 1 resource | Example: ```json { "sub": "gid://User/17", "scope": [ "read:organization:1:group:1:project:1:*", "read:organization:1:group:1:project:2:*", "read:organization:1:group:2:project:3" ] } ```