Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cassandra-schema.cql
Original file line number Diff line number Diff line change
Expand Up @@ -2032,6 +2032,7 @@ CREATE TABLE spar_test.issuer_idp (
CREATE TABLE spar_test.idp (
idp uuid PRIMARY KEY,
api_version int,
domain text,
extra_public_keys list<blob>,
handle text,
issuer text,
Expand Down
9 changes: 9 additions & 0 deletions changelog.d/2-features/multi-ingress-idp-domains
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
When a SAML IdP is created on a multi-ingress domain (implying that
multi-ingress domains are configured in Spar) the domain is added as `domain`
field to that IdP's `extraInfo` (`WireIdP` type in Haskell.) To avoid confusion
in later lookups, at most one IdP can be configured per multi-ingress domain.
If multi-ingress is not configured or it's not configured for the specific
domain, no `domain` field gets added to the IdP. This guards against creating
multiple IdPs and then assigning them to multi-ingress domains. Thus, users who
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean "guards against a team having no multi-ingress, but several idps that are only distinguished by domain (because domain is part of the database key)?

we do allow several idps per team, though, right? except there is some complication with scim and saml, and associating the two for one ipd, not sure what the current implementation status there is.

not sure this makes sense, but i'll leave it as a note to self so i can clarify later.

don't use multi-ingress don't observe any change. This feature only opens the
door to later provide an IdP for a multi-ingress domain.
34 changes: 34 additions & 0 deletions docs/src/developer/reference/config-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,40 @@ clear as there are multiple `ssoUri`s defined. So, the SCIM base URI needs to
be set explicitly in `scimBaseUri`. In spar's YAML config file `scimBaseUri` is
always required.

#### SAML IdPs

The multi-ingress configuration also affects SAML IdPs: The multi-ingress
domain (as specified by the internal `Z-Host` header; according to the domain
the `/identity-providers` endpoints are accessed on) is stored in the IdP's
configuration data. It can be observed as field `domain` in responses of
`/identity-providers`.

For example:
```json
{
"extraInfo": {
"apiVersion": "WireIdPAPIV2",
"domain": "nginz-https.ernie.example.com",
"handle": "IdP 1",
"oldIssuers": [
"https://issuer.net/_c4590f08-14da-446b-89d0-fcb46ac8ccf9"
],
"replacedBy": null,
"team": "ce2c2ade-8b93-4db3-b1d3-44ce1d987ca6"
},
"id": "ba6afb01-3edf-416c-8561-42e7ecc9b00a",
"metadata": {
"certAuthnResponse": [
"<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:X509Data><ds:X509Certificate>MIIBOTCBxKADAgECAg4TIFmNatMeqaAE8BWQBTANBgkqhkiG9w0BAQsFADAAMB4XDTI1MDkxODE2MjY1NVoXDTQ1MDkxMzE2MjY1NVowADB6MA0GCSqGSIb3DQEBAQUAA2kAMGYCYQC/KgI1kw9+dXc/XUQ8Q6no9GsT9gX1g3sekVEI7UuxrcHd+Tapzi1T99TdnBDedXCAxGTW6Rwhu3F20j0iAi0neWzi5xv+1KWxK0djzJ0Kxk5AcdDx/Tz+t1Uzd4VXkhECAREwDQYJKoZIhvcNAQELBQADYQAsFrbuDmGZphl9d9VdHyh8a9lIFh3oO5et+tPqFTTRPbbfEewqvtwFWvP9Gf1qgjk0qwKX3GDsFejQf4h94qU1Zf0IE8J/WyIiwEWRvZgAQ9UmqKljmbHKssyNwsl6tTY=</ds:X509Certificate></ds:X509Data></ds:KeyInfo>"
],
"issuer": "https://issuer.net/_2ab62f21-44c0-4c60-a115-d05b5129141d",
"requestURI": "https://requri.net/22169147-7d84-4991-9652-d7434986b7d8"
}
}
```

There can be at most one IdP per multi-ingress domain. Creating more returns an
error. Though, IdPs can be reconfigured as long as this invariant holds.

### Webapp

Expand Down
1 change: 1 addition & 0 deletions integration/integration.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ library
Test.Search
Test.Services
Test.Spar
Test.Spar.MultiIngressIdp
Test.Spar.MultiIngressSSO
Test.Spar.STM
Test.Swagger
Expand Down
40 changes: 31 additions & 9 deletions integration/test/API/Spar.hs
Original file line number Diff line number Diff line change
Expand Up @@ -92,25 +92,47 @@ updateScimUser domain scimToken userId scimUser = do
& addHeader "Accept" "application/scim+json"

createIdp :: (HasCallStack, MakesValue user) => user -> SAML.IdPMetadata -> App Response
createIdp user metadata = do
req <- baseRequest user Spar Versioned "/identity-providers"
submit "POST" $ req
& addQueryParams [("api_version", "v2")]
& addXML (fromLT $ SAML.encode metadata)
createIdp = (flip createIdpWithZHost) Nothing

createIdpWithZHost :: (HasCallStack, MakesValue user) => user -> Maybe String -> SAML.IdPMetadata -> App Response
createIdpWithZHost user mbZHost metadata = do
bReq <- baseRequest user Spar Versioned "/identity-providers"
let req =
bReq
& addQueryParams [("api_version", "v2")]
& addXML (fromLT $ SAML.encode metadata)
& addHeader "Content-Type" "application/xml"
submit "POST" (req & maybe id zHost mbZHost)

-- | https://staging-nginz-https.zinfra.io/v7/api/swagger-ui/#/default/idp-update
updateIdp :: (HasCallStack, MakesValue user) => user -> String -> SAML.IdPMetadata -> App Response
updateIdp user idpId metadata = do
req <- baseRequest user Spar Versioned $ joinHttpPath ["identity-providers", idpId]
submit "PUT" $ req
& addXML (fromLT $ SAML.encode metadata)
updateIdp = (flip updateIdpWithZHost) Nothing

updateIdpWithZHost :: (HasCallStack, MakesValue user) => user -> Maybe String -> String -> SAML.IdPMetadata -> App Response
updateIdpWithZHost user mbZHost idpId metadata = do
bReq <- baseRequest user Spar Versioned $ joinHttpPath ["identity-providers", idpId]
let req =
bReq
& addXML (fromLT $ SAML.encode metadata)
& addHeader "Content-Type" "application/xml"
submit "PUT" (req & maybe id zHost mbZHost)

-- | https://staging-nginz-https.zinfra.io/v7/api/swagger-ui/#/default/idp-get-all
getIdps :: (HasCallStack, MakesValue user) => user -> App Response
getIdps user = do
req <- baseRequest user Spar Versioned "/identity-providers"
submit "GET" req

getIdp :: (HasCallStack, MakesValue user) => user -> String -> App Response
getIdp user idpId = do
req <- baseRequest user Spar Versioned $ joinHttpPath ["identity-providers", idpId]
submit "GET" req

deleteIdp :: (HasCallStack, MakesValue user) => user -> String -> App Response
deleteIdp user idpId = do
req <- baseRequest user Spar Versioned $ joinHttpPath ["identity-providers", idpId]
submit "DELETE" req

-- | https://staging-nginz-https.zinfra.io/v7/api/swagger-ui/#/default/sso-team-metadata
getSPMetadata :: (HasCallStack, MakesValue domain) => domain -> String -> App Response
getSPMetadata = (flip getSPMetadataWithZHost) Nothing
Expand Down
Loading