From 710d5ffd8bab7a4868ff8a43ac9322f46d2ba1d5 Mon Sep 17 00:00:00 2001 From: Scott Reisor Date: Wed, 7 Aug 2019 12:21:55 -0400 Subject: [PATCH 1/8] support for saml idp initiated flow --- connector/saml/saml.go | 15 +++++++ server/handlers.go | 100 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/connector/saml/saml.go b/connector/saml/saml.go index 1c1cb46087..7f7c8ee42c 100644 --- a/connector/saml/saml.go +++ b/connector/saml/saml.go @@ -434,6 +434,21 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str return ident, nil } +// GetSAMLIssuer parses a SAML response and returns its 'Issuer' field. +func GetSAMLIssuer(samlResponse string) (string, error) { + rawResp, err := base64.StdEncoding.DecodeString(samlResponse) + if err != nil { + return "", err + } + + var resp response + if err := xml.Unmarshal(rawResp, &resp); err != nil { + return "", err + } + + return resp.Issuer.Issuer, nil +} + // validateStatus verifies that the response has a good status code or // formats a human readble error based on the bad status. func (p *provider) validateStatus(status *status) error { diff --git a/server/handlers.go b/server/handlers.go index 2b033c870a..70f7b3079f 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -19,6 +19,7 @@ import ( jose "gopkg.in/square/go-jose.v2" "github.com/dexidp/dex/connector" + "github.com/dexidp/dex/connector/saml" "github.com/dexidp/dex/server/internal" "github.com/dexidp/dex/storage" ) @@ -395,7 +396,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) { } func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) { - var authID string + var authID, samlInResponseTo string switch r.Method { case http.MethodGet: // OAuth2 callback if authID = r.URL.Query().Get("state"); authID == "" { @@ -403,8 +404,11 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) return } case http.MethodPost: // SAML POST binding - if authID = r.PostFormValue("RelayState"); authID == "" { - s.renderError(r, w, http.StatusBadRequest, "User session error.") + var code int + var err error + authID, samlInResponseTo, code, err = s.handleSamlCallback(r) + if err != nil { + s.renderError(w, code, fmt.Sprintf("Error processing SAML callback: %s.", err)) return } default: @@ -452,7 +456,7 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) s.renderError(r, w, http.StatusBadRequest, "Invalid request") return } - identity, err = conn.HandlePOST(parseScopes(authReq.Scopes), r.PostFormValue("SAMLResponse"), authReq.ID) + identity, err = conn.HandlePOST(parseScopes(authReq.Scopes), r.PostFormValue("SAMLResponse"), samlInResponseTo) default: s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return @@ -474,6 +478,94 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) http.Redirect(w, r, redirectURL, http.StatusSeeOther) } +// handleSamlCallback handles a saml callback response with support for the IdP initiated flow. +// if the RelayState is not one of our auth requests, we check to see if it is a valid redirectURI +// for one of our clients. then, if the SAMLResponse is from a registered connector, we create a +// new auth request in the database so we can contuine with our regular callback flow. +func (s *Server) handleSamlCallback(r *http.Request) (string, string, int, error) { + relayState := r.PostFormValue("RelayState") + if relayState == "" { + return "", "", http.StatusBadRequest, fmt.Errorf("user session error") + } + // if our relaySate is a valid authId, this is not IdP initiated + if _, err := s.storage.GetAuthRequest(relayState); err == nil { + return relayState, relayState, http.StatusOK, nil + } else if err != storage.ErrNotFound { + s.logger.Errorf("Failed to get auth request: %v", err) + return "", "", http.StatusInternalServerError, fmt.Errorf("database error") + } + // find the client the RelayState is trying to send us to, stop when we match a redirectURI + // TODO: is it valid for differrent clients to have the same redirectURI? ¯\_(ツ)_/¯ + clients, err := s.storage.ListClients() + if err != nil { + s.logger.Errorf("Failed to list clients: %v", err) + return "", "", http.StatusInternalServerError, fmt.Errorf("database error") + } + var client *storage.Client + for _, c := range clients { + for _, u := range c.RedirectURIs { + if relayState == u { + client = &c + break + } + } + } + if client == nil { + s.logger.Errorf("Cannot find client with redirectURI: %s", relayState) + return "", "", http.StatusInternalServerError, fmt.Errorf("bad saml response") + } + // find the issuer attribute in the SAMLResponse, use it to find the associated connector-id. + ssoIssuer, err := saml.GetSAMLIssuer(r.PostFormValue("SAMLResponse")) + if err != nil { + return "", "", http.StatusBadRequest, fmt.Errorf("bad saml response") + } + connectors, err := s.storage.ListConnectors() + if err != nil { + s.logger.Errorf("Failed to list connectors: %v", err) + return "", "", http.StatusInternalServerError, fmt.Errorf("database error") + } + var connID string + for _, c := range connectors { + if c.Type != "saml" { + continue + } + var cfg saml.Config + if err := json.Unmarshal(c.Config, &cfg); err != nil { + s.logger.Errorf("Cannot parse saml config for connector %s: %s", c.ID, err) + return "", "", http.StatusPreconditionFailed, fmt.Errorf("config error") + } + if cfg.SSOIssuer == ssoIssuer { + connID = c.ID + break + } + } + if connID == "" { + s.logger.Errorf("Cannot find the connector id associated with the issuer %s", ssoIssuer) + return "", "", http.StatusInternalServerError, fmt.Errorf("bad saml response") + } + // build our auth request (fake it til ya make it) + authID := storage.NewID() + req := storage.AuthRequest{ + ID: authID, + ClientID: client.ID, + State: "", + Nonce: "", + ForceApprovalPrompt: false, + // TODO: make IdP initiated scopes configurable as part of the saml connector + Scopes: []string{"openid", "profile", "email", "groups"}, + RedirectURI: relayState, + ResponseTypes: []string{"code"}, + ConnectorID: connID, + Expiry: s.now().Add(s.authRequestsValidFor), + } + if err := s.storage.CreateAuthRequest(req); err != nil { + s.logger.Errorf("Could not create SAML IDP initiated request: %v", err) + return "", "", http.StatusInternalServerError, fmt.Errorf("database error") + } + // empty InResponseTo value because this is IdP initiated, not in response to any request + return authID, "", http.StatusOK, nil +} + // finalizeLogin associates the user's identity with the current AuthRequest, then returns // the approval page's path. func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.AuthRequest, conn connector.Connector) (string, error) { From 823ada63a0a254e2633c28bc8d686d5d19d7ac41 Mon Sep 17 00:00:00 2001 From: Scott Reisor Date: Thu, 15 Aug 2019 11:16:26 -0400 Subject: [PATCH 2/8] initial custom IdP SAML scope changes --- connector/saml/saml.go | 8 +++++ server/handlers.go | 75 +++++++++++++++++++++--------------------- storage/storage.go | 9 +++++ 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/connector/saml/saml.go b/connector/saml/saml.go index 7f7c8ee42c..a6ea6fadd6 100644 --- a/connector/saml/saml.go +++ b/connector/saml/saml.go @@ -48,6 +48,10 @@ const ( ) var ( + // DefaultIDPInitiatedScopes specifies scopes to use for IdP initiated flows + // if the target client does not have any configured + DefaultIDPInitiatedScopes = []string{"openid", "profile", "email", "groups"} + nameIDFormats = []string{ nameIDFormatEmailAddress, nameIDFormatUnspecified, @@ -446,6 +450,10 @@ func GetSAMLIssuer(samlResponse string) (string, error) { return "", err } + if resp.Issuer == nil { + return "", errors.New("response is missing issuer") + } + return resp.Issuer.Issuer, nil } diff --git a/server/handlers.go b/server/handlers.go index 70f7b3079f..e956150549 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -406,7 +406,7 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) case http.MethodPost: // SAML POST binding var code int var err error - authID, samlInResponseTo, code, err = s.handleSamlCallback(r) + authID, samlInResponseTo, code, err = s.handleSAMLCallback(r) if err != nil { s.renderError(w, code, fmt.Sprintf("Error processing SAML callback: %s.", err)) return @@ -481,48 +481,51 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) // handleSamlCallback handles a saml callback response with support for the IdP initiated flow. // if the RelayState is not one of our auth requests, we check to see if it is a valid redirectURI // for one of our clients. then, if the SAMLResponse is from a registered connector, we create a -// new auth request in the database so we can contuine with our regular callback flow. -func (s *Server) handleSamlCallback(r *http.Request) (string, string, int, error) { +// new auth request in the database so we can continue with our regular callback flow. +func (s *Server) handleSAMLCallback(r *http.Request) (string, string, int, error) { relayState := r.PostFormValue("RelayState") if relayState == "" { - return "", "", http.StatusBadRequest, fmt.Errorf("user session error") + return "", "", http.StatusBadRequest, errors.New("user session error") } // if our relaySate is a valid authId, this is not IdP initiated if _, err := s.storage.GetAuthRequest(relayState); err == nil { return relayState, relayState, http.StatusOK, nil } else if err != storage.ErrNotFound { s.logger.Errorf("Failed to get auth request: %v", err) - return "", "", http.StatusInternalServerError, fmt.Errorf("database error") - } - // find the client the RelayState is trying to send us to, stop when we match a redirectURI - // TODO: is it valid for differrent clients to have the same redirectURI? ¯\_(ツ)_/¯ - clients, err := s.storage.ListClients() - if err != nil { - s.logger.Errorf("Failed to list clients: %v", err) - return "", "", http.StatusInternalServerError, fmt.Errorf("database error") - } - var client *storage.Client - for _, c := range clients { - for _, u := range c.RedirectURIs { - if relayState == u { - client = &c - break - } - } - } - if client == nil { - s.logger.Errorf("Cannot find client with redirectURI: %s", relayState) - return "", "", http.StatusInternalServerError, fmt.Errorf("bad saml response") + return "", "", http.StatusInternalServerError, errors.New("database error") + } + // if relayState is a valid client_id, we'll check to see if that has config to + // allow the IdP initiated flow. + client, err := s.storage.GetClient(relayState) + if err == storage.ErrNotFound { + // this is not a valid client_id, we have a bogus relayState + s.logger.Warnf("SAML authentication recieved with unknown RelaySate %q", relayState) + return "", "", http.StatusBadRequest, errors.New("user session error") + } else if err != nil { + s.logger.Errorf("Failed to get client: %v", err) + return "", "", http.StatusInternalServerError, errors.New("database error") + } + redirectURI := client.SAMLInitiated.RedirectURI + if redirectURI == "" { + // this client does not have support for IdP initiated login configured + s.logger.Warnf("SAML IdP initiated login attempt made to client %q, but support for this flow is not configured. Update the client config to support this login flow.", relayState) + return "", "", http.StatusBadRequest, errors.New("user session error") + } + // TODO: should we check that 'idtoken' is one of the scopes? or just put the + // burden on the user to confiugre these scopes correctly? + scopes := client.SAMLInitiated.Scopes + if len(scopes) == 0 { + scopes = saml.DefaultIDPInitiatedScopes } // find the issuer attribute in the SAMLResponse, use it to find the associated connector-id. ssoIssuer, err := saml.GetSAMLIssuer(r.PostFormValue("SAMLResponse")) if err != nil { - return "", "", http.StatusBadRequest, fmt.Errorf("bad saml response") + return "", "", http.StatusBadRequest, errors.New("bad saml response") } connectors, err := s.storage.ListConnectors() if err != nil { s.logger.Errorf("Failed to list connectors: %v", err) - return "", "", http.StatusInternalServerError, fmt.Errorf("database error") + return "", "", http.StatusInternalServerError, errors.New("database error") } var connID string for _, c := range connectors { @@ -532,7 +535,7 @@ func (s *Server) handleSamlCallback(r *http.Request) (string, string, int, error var cfg saml.Config if err := json.Unmarshal(c.Config, &cfg); err != nil { s.logger.Errorf("Cannot parse saml config for connector %s: %s", c.ID, err) - return "", "", http.StatusPreconditionFailed, fmt.Errorf("config error") + return "", "", http.StatusPreconditionFailed, errors.New("config error") } if cfg.SSOIssuer == ssoIssuer { connID = c.ID @@ -541,26 +544,22 @@ func (s *Server) handleSamlCallback(r *http.Request) (string, string, int, error } if connID == "" { s.logger.Errorf("Cannot find the connector id associated with the issuer %s", ssoIssuer) - return "", "", http.StatusInternalServerError, fmt.Errorf("bad saml response") + return "", "", http.StatusInternalServerError, errors.New("bad saml response") } // build our auth request (fake it til ya make it) authID := storage.NewID() req := storage.AuthRequest{ - ID: authID, - ClientID: client.ID, - State: "", - Nonce: "", - ForceApprovalPrompt: false, - // TODO: make IdP initiated scopes configurable as part of the saml connector - Scopes: []string{"openid", "profile", "email", "groups"}, - RedirectURI: relayState, + ID: authID, + ClientID: client.ID, + Scopes: scopes, + RedirectURI: redirectURI, ResponseTypes: []string{"code"}, ConnectorID: connID, Expiry: s.now().Add(s.authRequestsValidFor), } if err := s.storage.CreateAuthRequest(req); err != nil { s.logger.Errorf("Could not create SAML IDP initiated request: %v", err) - return "", "", http.StatusInternalServerError, fmt.Errorf("database error") + return "", "", http.StatusInternalServerError, errors.New("database error") } // empty InResponseTo value because this is IdP initiated, not in response to any request return authID, "", http.StatusOK, nil diff --git a/storage/storage.go b/storage/storage.go index 5bbb2b3f1b..26b07db3e4 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -135,6 +135,15 @@ type Client struct { // Name and LogoURL used when displaying this client to the end user. Name string `json:"name" yaml:"name"` LogoURL string `json:"logoURL" yaml:"logoURL"` + + SAMLInitiated SAMLInitiatedConfig `json:"samlInitiated" yaml:"samlInitiated"` +} + +// SAMLInitiatedConfig provides configuration for how a client should be treated when SAML's +// Identity Provider initiated login is used. +type SAMLInitiatedConfig struct { + RedirectURI string `json:"redirectURI" yaml:"redirectURI"` + Scopes []string `json:"scopes" yaml:"scopes"` } // Claims represents the ID Token claims supported by the server. From 574b9306ed19c4ac5f2f006af0ef38879cd47602 Mon Sep 17 00:00:00 2001 From: Scott Reisor Date: Mon, 19 Aug 2019 15:57:10 -0400 Subject: [PATCH 3/8] updated storage connectors for client changes (needs testing) --- api/api.pb.go | 269 ++++++++++++++++++++++-------------- api/api.proto | 18 ++- server/api.go | 12 ++ storage/kubernetes/types.go | 16 +++ storage/sql/crud.go | 27 ++-- storage/sql/migrate.go | 9 ++ 6 files changed, 236 insertions(+), 115 deletions(-) diff --git a/api/api.pb.go b/api/api.pb.go index 5bac4e9514..b71c4d2fb8 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -26,16 +26,17 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package // Client represents an OAuth2 client. type Client struct { - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Secret string `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` - RedirectUris []string `protobuf:"bytes,3,rep,name=redirect_uris,json=redirectUris,proto3" json:"redirect_uris,omitempty"` - TrustedPeers []string `protobuf:"bytes,4,rep,name=trusted_peers,json=trustedPeers,proto3" json:"trusted_peers,omitempty"` - Public bool `protobuf:"varint,5,opt,name=public,proto3" json:"public,omitempty"` - Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` - LogoUrl string `protobuf:"bytes,7,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Secret string `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` + RedirectUris []string `protobuf:"bytes,3,rep,name=redirect_uris,json=redirectUris,proto3" json:"redirect_uris,omitempty"` + TrustedPeers []string `protobuf:"bytes,4,rep,name=trusted_peers,json=trustedPeers,proto3" json:"trusted_peers,omitempty"` + Public bool `protobuf:"varint,5,opt,name=public,proto3" json:"public,omitempty"` + Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` + LogoUrl string `protobuf:"bytes,7,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` + SamlInitiated *SamlInitiatedConfig `protobuf:"bytes,8,opt,name=saml_initiated,json=samlInitiated,proto3" json:"saml_initiated,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *Client) Reset() { *m = Client{} } @@ -112,6 +113,61 @@ func (m *Client) GetLogoUrl() string { return "" } +func (m *Client) GetSamlInitiated() *SamlInitiatedConfig { + if m != nil { + return m.SamlInitiated + } + return nil +} + +// SamlInitiatedConfig provides config options for SAML initiated login on a client +type SamlInitiatedConfig struct { + RedirectUri string `protobuf:"bytes,1,opt,name=redirect_uri,json=redirectUri,proto3" json:"redirect_uri,omitempty"` + Scopes []string `protobuf:"bytes,2,rep,name=scopes,proto3" json:"scopes,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SamlInitiatedConfig) Reset() { *m = SamlInitiatedConfig{} } +func (m *SamlInitiatedConfig) String() string { return proto.CompactTextString(m) } +func (*SamlInitiatedConfig) ProtoMessage() {} +func (*SamlInitiatedConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_1b40cafcd4234784, []int{1} +} + +func (m *SamlInitiatedConfig) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SamlInitiatedConfig.Unmarshal(m, b) +} +func (m *SamlInitiatedConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SamlInitiatedConfig.Marshal(b, m, deterministic) +} +func (m *SamlInitiatedConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_SamlInitiatedConfig.Merge(m, src) +} +func (m *SamlInitiatedConfig) XXX_Size() int { + return xxx_messageInfo_SamlInitiatedConfig.Size(m) +} +func (m *SamlInitiatedConfig) XXX_DiscardUnknown() { + xxx_messageInfo_SamlInitiatedConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_SamlInitiatedConfig proto.InternalMessageInfo + +func (m *SamlInitiatedConfig) GetRedirectUri() string { + if m != nil { + return m.RedirectUri + } + return "" +} + +func (m *SamlInitiatedConfig) GetScopes() []string { + if m != nil { + return m.Scopes + } + return nil +} + // CreateClientReq is a request to make a client. type CreateClientReq struct { Client *Client `protobuf:"bytes,1,opt,name=client,proto3" json:"client,omitempty"` @@ -124,7 +180,7 @@ func (m *CreateClientReq) Reset() { *m = CreateClientReq{} } func (m *CreateClientReq) String() string { return proto.CompactTextString(m) } func (*CreateClientReq) ProtoMessage() {} func (*CreateClientReq) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{1} + return fileDescriptor_1b40cafcd4234784, []int{2} } func (m *CreateClientReq) XXX_Unmarshal(b []byte) error { @@ -165,7 +221,7 @@ func (m *CreateClientResp) Reset() { *m = CreateClientResp{} } func (m *CreateClientResp) String() string { return proto.CompactTextString(m) } func (*CreateClientResp) ProtoMessage() {} func (*CreateClientResp) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{2} + return fileDescriptor_1b40cafcd4234784, []int{3} } func (m *CreateClientResp) XXX_Unmarshal(b []byte) error { @@ -213,7 +269,7 @@ func (m *DeleteClientReq) Reset() { *m = DeleteClientReq{} } func (m *DeleteClientReq) String() string { return proto.CompactTextString(m) } func (*DeleteClientReq) ProtoMessage() {} func (*DeleteClientReq) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{3} + return fileDescriptor_1b40cafcd4234784, []int{4} } func (m *DeleteClientReq) XXX_Unmarshal(b []byte) error { @@ -253,7 +309,7 @@ func (m *DeleteClientResp) Reset() { *m = DeleteClientResp{} } func (m *DeleteClientResp) String() string { return proto.CompactTextString(m) } func (*DeleteClientResp) ProtoMessage() {} func (*DeleteClientResp) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{4} + return fileDescriptor_1b40cafcd4234784, []int{5} } func (m *DeleteClientResp) XXX_Unmarshal(b []byte) error { @@ -283,21 +339,22 @@ func (m *DeleteClientResp) GetNotFound() bool { // UpdateClientReq is a request to update an exisitng client. type UpdateClientReq struct { - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - RedirectUris []string `protobuf:"bytes,2,rep,name=redirect_uris,json=redirectUris,proto3" json:"redirect_uris,omitempty"` - TrustedPeers []string `protobuf:"bytes,3,rep,name=trusted_peers,json=trustedPeers,proto3" json:"trusted_peers,omitempty"` - Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` - LogoUrl string `protobuf:"bytes,5,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + RedirectUris []string `protobuf:"bytes,2,rep,name=redirect_uris,json=redirectUris,proto3" json:"redirect_uris,omitempty"` + TrustedPeers []string `protobuf:"bytes,3,rep,name=trusted_peers,json=trustedPeers,proto3" json:"trusted_peers,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + LogoUrl string `protobuf:"bytes,5,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"` + SamlInitiated *SamlInitiatedConfig `protobuf:"bytes,6,opt,name=saml_initiated,json=samlInitiated,proto3" json:"saml_initiated,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *UpdateClientReq) Reset() { *m = UpdateClientReq{} } func (m *UpdateClientReq) String() string { return proto.CompactTextString(m) } func (*UpdateClientReq) ProtoMessage() {} func (*UpdateClientReq) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{5} + return fileDescriptor_1b40cafcd4234784, []int{6} } func (m *UpdateClientReq) XXX_Unmarshal(b []byte) error { @@ -353,6 +410,13 @@ func (m *UpdateClientReq) GetLogoUrl() string { return "" } +func (m *UpdateClientReq) GetSamlInitiated() *SamlInitiatedConfig { + if m != nil { + return m.SamlInitiated + } + return nil +} + // UpdateClientResp returns the reponse form updating a client. type UpdateClientResp struct { NotFound bool `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"` @@ -365,7 +429,7 @@ func (m *UpdateClientResp) Reset() { *m = UpdateClientResp{} } func (m *UpdateClientResp) String() string { return proto.CompactTextString(m) } func (*UpdateClientResp) ProtoMessage() {} func (*UpdateClientResp) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{6} + return fileDescriptor_1b40cafcd4234784, []int{7} } func (m *UpdateClientResp) XXX_Unmarshal(b []byte) error { @@ -409,7 +473,7 @@ func (m *Password) Reset() { *m = Password{} } func (m *Password) String() string { return proto.CompactTextString(m) } func (*Password) ProtoMessage() {} func (*Password) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{7} + return fileDescriptor_1b40cafcd4234784, []int{8} } func (m *Password) XXX_Unmarshal(b []byte) error { @@ -470,7 +534,7 @@ func (m *CreatePasswordReq) Reset() { *m = CreatePasswordReq{} } func (m *CreatePasswordReq) String() string { return proto.CompactTextString(m) } func (*CreatePasswordReq) ProtoMessage() {} func (*CreatePasswordReq) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{8} + return fileDescriptor_1b40cafcd4234784, []int{9} } func (m *CreatePasswordReq) XXX_Unmarshal(b []byte) error { @@ -510,7 +574,7 @@ func (m *CreatePasswordResp) Reset() { *m = CreatePasswordResp{} } func (m *CreatePasswordResp) String() string { return proto.CompactTextString(m) } func (*CreatePasswordResp) ProtoMessage() {} func (*CreatePasswordResp) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{9} + return fileDescriptor_1b40cafcd4234784, []int{10} } func (m *CreatePasswordResp) XXX_Unmarshal(b []byte) error { @@ -553,7 +617,7 @@ func (m *UpdatePasswordReq) Reset() { *m = UpdatePasswordReq{} } func (m *UpdatePasswordReq) String() string { return proto.CompactTextString(m) } func (*UpdatePasswordReq) ProtoMessage() {} func (*UpdatePasswordReq) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{10} + return fileDescriptor_1b40cafcd4234784, []int{11} } func (m *UpdatePasswordReq) XXX_Unmarshal(b []byte) error { @@ -607,7 +671,7 @@ func (m *UpdatePasswordResp) Reset() { *m = UpdatePasswordResp{} } func (m *UpdatePasswordResp) String() string { return proto.CompactTextString(m) } func (*UpdatePasswordResp) ProtoMessage() {} func (*UpdatePasswordResp) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{11} + return fileDescriptor_1b40cafcd4234784, []int{12} } func (m *UpdatePasswordResp) XXX_Unmarshal(b []byte) error { @@ -647,7 +711,7 @@ func (m *DeletePasswordReq) Reset() { *m = DeletePasswordReq{} } func (m *DeletePasswordReq) String() string { return proto.CompactTextString(m) } func (*DeletePasswordReq) ProtoMessage() {} func (*DeletePasswordReq) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{12} + return fileDescriptor_1b40cafcd4234784, []int{13} } func (m *DeletePasswordReq) XXX_Unmarshal(b []byte) error { @@ -687,7 +751,7 @@ func (m *DeletePasswordResp) Reset() { *m = DeletePasswordResp{} } func (m *DeletePasswordResp) String() string { return proto.CompactTextString(m) } func (*DeletePasswordResp) ProtoMessage() {} func (*DeletePasswordResp) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{13} + return fileDescriptor_1b40cafcd4234784, []int{14} } func (m *DeletePasswordResp) XXX_Unmarshal(b []byte) error { @@ -726,7 +790,7 @@ func (m *ListPasswordReq) Reset() { *m = ListPasswordReq{} } func (m *ListPasswordReq) String() string { return proto.CompactTextString(m) } func (*ListPasswordReq) ProtoMessage() {} func (*ListPasswordReq) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{14} + return fileDescriptor_1b40cafcd4234784, []int{15} } func (m *ListPasswordReq) XXX_Unmarshal(b []byte) error { @@ -759,7 +823,7 @@ func (m *ListPasswordResp) Reset() { *m = ListPasswordResp{} } func (m *ListPasswordResp) String() string { return proto.CompactTextString(m) } func (*ListPasswordResp) ProtoMessage() {} func (*ListPasswordResp) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{15} + return fileDescriptor_1b40cafcd4234784, []int{16} } func (m *ListPasswordResp) XXX_Unmarshal(b []byte) error { @@ -798,7 +862,7 @@ func (m *VersionReq) Reset() { *m = VersionReq{} } func (m *VersionReq) String() string { return proto.CompactTextString(m) } func (*VersionReq) ProtoMessage() {} func (*VersionReq) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{16} + return fileDescriptor_1b40cafcd4234784, []int{17} } func (m *VersionReq) XXX_Unmarshal(b []byte) error { @@ -835,7 +899,7 @@ func (m *VersionResp) Reset() { *m = VersionResp{} } func (m *VersionResp) String() string { return proto.CompactTextString(m) } func (*VersionResp) ProtoMessage() {} func (*VersionResp) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{17} + return fileDescriptor_1b40cafcd4234784, []int{18} } func (m *VersionResp) XXX_Unmarshal(b []byte) error { @@ -886,7 +950,7 @@ func (m *RefreshTokenRef) Reset() { *m = RefreshTokenRef{} } func (m *RefreshTokenRef) String() string { return proto.CompactTextString(m) } func (*RefreshTokenRef) ProtoMessage() {} func (*RefreshTokenRef) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{18} + return fileDescriptor_1b40cafcd4234784, []int{19} } func (m *RefreshTokenRef) XXX_Unmarshal(b []byte) error { @@ -948,7 +1012,7 @@ func (m *ListRefreshReq) Reset() { *m = ListRefreshReq{} } func (m *ListRefreshReq) String() string { return proto.CompactTextString(m) } func (*ListRefreshReq) ProtoMessage() {} func (*ListRefreshReq) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{19} + return fileDescriptor_1b40cafcd4234784, []int{20} } func (m *ListRefreshReq) XXX_Unmarshal(b []byte) error { @@ -988,7 +1052,7 @@ func (m *ListRefreshResp) Reset() { *m = ListRefreshResp{} } func (m *ListRefreshResp) String() string { return proto.CompactTextString(m) } func (*ListRefreshResp) ProtoMessage() {} func (*ListRefreshResp) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{20} + return fileDescriptor_1b40cafcd4234784, []int{21} } func (m *ListRefreshResp) XXX_Unmarshal(b []byte) error { @@ -1030,7 +1094,7 @@ func (m *RevokeRefreshReq) Reset() { *m = RevokeRefreshReq{} } func (m *RevokeRefreshReq) String() string { return proto.CompactTextString(m) } func (*RevokeRefreshReq) ProtoMessage() {} func (*RevokeRefreshReq) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{21} + return fileDescriptor_1b40cafcd4234784, []int{22} } func (m *RevokeRefreshReq) XXX_Unmarshal(b []byte) error { @@ -1078,7 +1142,7 @@ func (m *RevokeRefreshResp) Reset() { *m = RevokeRefreshResp{} } func (m *RevokeRefreshResp) String() string { return proto.CompactTextString(m) } func (*RevokeRefreshResp) ProtoMessage() {} func (*RevokeRefreshResp) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{22} + return fileDescriptor_1b40cafcd4234784, []int{23} } func (m *RevokeRefreshResp) XXX_Unmarshal(b []byte) error { @@ -1118,7 +1182,7 @@ func (m *VerifyPasswordReq) Reset() { *m = VerifyPasswordReq{} } func (m *VerifyPasswordReq) String() string { return proto.CompactTextString(m) } func (*VerifyPasswordReq) ProtoMessage() {} func (*VerifyPasswordReq) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{23} + return fileDescriptor_1b40cafcd4234784, []int{24} } func (m *VerifyPasswordReq) XXX_Unmarshal(b []byte) error { @@ -1165,7 +1229,7 @@ func (m *VerifyPasswordResp) Reset() { *m = VerifyPasswordResp{} } func (m *VerifyPasswordResp) String() string { return proto.CompactTextString(m) } func (*VerifyPasswordResp) ProtoMessage() {} func (*VerifyPasswordResp) Descriptor() ([]byte, []int) { - return fileDescriptor_1b40cafcd4234784, []int{24} + return fileDescriptor_1b40cafcd4234784, []int{25} } func (m *VerifyPasswordResp) XXX_Unmarshal(b []byte) error { @@ -1202,6 +1266,7 @@ func (m *VerifyPasswordResp) GetNotFound() bool { func init() { proto.RegisterType((*Client)(nil), "api.Client") + proto.RegisterType((*SamlInitiatedConfig)(nil), "api.SamlInitiatedConfig") proto.RegisterType((*CreateClientReq)(nil), "api.CreateClientReq") proto.RegisterType((*CreateClientResp)(nil), "api.CreateClientResp") proto.RegisterType((*DeleteClientReq)(nil), "api.DeleteClientReq") @@ -1231,64 +1296,68 @@ func init() { func init() { proto.RegisterFile("api/api.proto", fileDescriptor_1b40cafcd4234784) } var fileDescriptor_1b40cafcd4234784 = []byte{ - // 905 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0xeb, 0x6e, 0xdb, 0x36, - 0x14, 0xb6, 0xad, 0xd8, 0x96, 0x8f, 0xef, 0x9c, 0x9b, 0xba, 0x2e, 0x06, 0xa4, 0x2c, 0x06, 0xa4, - 0x18, 0xe0, 0xac, 0x1d, 0xb0, 0x01, 0x2b, 0xd6, 0x5d, 0xd2, 0x6e, 0x2d, 0xb0, 0x0d, 0x85, 0x30, - 0xe7, 0xe7, 0x04, 0xc5, 0x3a, 0x4e, 0x88, 0x28, 0x92, 0x46, 0xd2, 0x71, 0xb2, 0x47, 0xd9, 0xdb, - 0xec, 0xd7, 0x5e, 0xab, 0x20, 0x45, 0x29, 0xba, 0x38, 0x71, 0xfe, 0xf9, 0x7c, 0xe2, 0xb9, 0x7d, - 0x87, 0xe7, 0xa3, 0xa1, 0xef, 0xc5, 0xec, 0xc8, 0x8b, 0xd9, 0x3c, 0xe6, 0x91, 0x8c, 0x88, 0xe5, - 0xc5, 0x8c, 0xfe, 0x57, 0x87, 0xd6, 0x71, 0xc0, 0x30, 0x94, 0x64, 0x00, 0x0d, 0xe6, 0x4f, 0xeb, - 0x07, 0xf5, 0xc3, 0x8e, 0xd3, 0x60, 0x3e, 0xd9, 0x87, 0x96, 0xc0, 0x25, 0x47, 0x39, 0x6d, 0x68, - 0xcc, 0x58, 0xe4, 0x39, 0xf4, 0x39, 0xfa, 0x8c, 0xe3, 0x52, 0xba, 0x6b, 0xce, 0xc4, 0xd4, 0x3a, - 0xb0, 0x0e, 0x3b, 0x4e, 0x2f, 0x05, 0x17, 0x9c, 0x09, 0x75, 0x48, 0xf2, 0xb5, 0x90, 0xe8, 0xbb, - 0x31, 0x22, 0x17, 0xd3, 0xbd, 0xe4, 0x90, 0x01, 0x3f, 0x2a, 0x4c, 0x65, 0x88, 0xd7, 0xa7, 0x01, - 0x5b, 0x4e, 0x9b, 0x07, 0xf5, 0x43, 0xdb, 0x31, 0x16, 0x21, 0xb0, 0x17, 0x7a, 0x97, 0x38, 0x6d, - 0xe9, 0xbc, 0xfa, 0x37, 0x79, 0x02, 0x76, 0x10, 0x9d, 0x45, 0xee, 0x9a, 0x07, 0xd3, 0xb6, 0xc6, - 0xdb, 0xca, 0x5e, 0xf0, 0x80, 0x7e, 0x03, 0xc3, 0x63, 0x8e, 0x9e, 0xc4, 0xa4, 0x11, 0x07, 0xff, - 0x26, 0xcf, 0xa1, 0xb5, 0xd4, 0x86, 0xee, 0xa7, 0xfb, 0xaa, 0x3b, 0x57, 0x7d, 0x9b, 0xef, 0xe6, - 0x13, 0xfd, 0x0b, 0x46, 0x45, 0x3f, 0x11, 0x93, 0x2f, 0x60, 0xe0, 0x05, 0x1c, 0x3d, 0xff, 0xc6, - 0xc5, 0x6b, 0x26, 0xa4, 0xd0, 0x01, 0x6c, 0xa7, 0x6f, 0xd0, 0x77, 0x1a, 0xcc, 0xc5, 0x6f, 0xdc, - 0x1d, 0xff, 0x19, 0x0c, 0xdf, 0x62, 0x80, 0xf9, 0xba, 0x4a, 0x1c, 0xd3, 0x23, 0x18, 0x15, 0x8f, - 0x88, 0x98, 0x3c, 0x85, 0x4e, 0x18, 0x49, 0x77, 0x15, 0xad, 0x43, 0xdf, 0x64, 0xb7, 0xc3, 0x48, - 0xfe, 0xa2, 0x6c, 0xfa, 0x6f, 0x1d, 0x86, 0x8b, 0xd8, 0xf7, 0xee, 0x09, 0x5a, 0x1d, 0x50, 0xe3, - 0x21, 0x03, 0xb2, 0xb6, 0x0c, 0x28, 0x1d, 0xc4, 0xde, 0x1d, 0x83, 0x68, 0x16, 0x07, 0x71, 0x04, - 0xa3, 0x62, 0x6d, 0xbb, 0xba, 0x61, 0x60, 0x7f, 0xf4, 0x84, 0xd8, 0x44, 0xdc, 0x27, 0x13, 0x68, - 0xe2, 0xa5, 0xc7, 0x02, 0xd3, 0x48, 0x62, 0xa8, 0x0a, 0xce, 0x3d, 0x71, 0xae, 0x69, 0xee, 0x39, - 0xfa, 0x37, 0x99, 0x81, 0xbd, 0x16, 0xc8, 0x75, 0x65, 0x96, 0x3e, 0x9c, 0xd9, 0xe4, 0x31, 0xb4, - 0xd5, 0x6f, 0x97, 0xf9, 0xa6, 0xe8, 0x96, 0x32, 0x3f, 0xf8, 0xf4, 0x0d, 0x8c, 0x93, 0x61, 0xa7, - 0x09, 0x15, 0x73, 0x2f, 0xc0, 0x8e, 0x8d, 0x69, 0x2e, 0x4a, 0x5f, 0x0f, 0x32, 0x3b, 0x93, 0x7d, - 0xa6, 0xaf, 0x81, 0x94, 0xfd, 0x1f, 0x7c, 0x5d, 0xe8, 0x19, 0x8c, 0x13, 0x62, 0xf2, 0xc9, 0xb7, - 0x37, 0xfc, 0x04, 0xec, 0x10, 0x37, 0x6e, 0xae, 0xe9, 0x76, 0x88, 0x9b, 0xf7, 0xaa, 0xef, 0x67, - 0xd0, 0x53, 0x9f, 0x4a, 0xbd, 0x77, 0x43, 0xdc, 0x2c, 0x0c, 0x44, 0x5f, 0x02, 0x29, 0x27, 0xda, - 0x35, 0x83, 0x17, 0x30, 0x4e, 0xae, 0xe0, 0xce, 0xda, 0x54, 0xf4, 0xf2, 0xd1, 0x5d, 0xd1, 0xc7, - 0x30, 0xfc, 0x8d, 0x09, 0x99, 0x8b, 0x4d, 0x7f, 0x80, 0x51, 0x11, 0x12, 0x31, 0xf9, 0x12, 0x3a, - 0x29, 0xd3, 0x8a, 0x42, 0xab, 0x3a, 0x89, 0xdb, 0xef, 0xb4, 0x07, 0x70, 0x82, 0x5c, 0xb0, 0x28, - 0x54, 0xe1, 0xbe, 0x85, 0x6e, 0x66, 0x89, 0x38, 0x51, 0x2d, 0x7e, 0x85, 0xdc, 0x94, 0x6e, 0x2c, - 0x32, 0x02, 0xa5, 0x77, 0x9a, 0xd2, 0xa6, 0xa3, 0xa5, 0xef, 0x1f, 0x18, 0x3a, 0xb8, 0xe2, 0x28, - 0xce, 0xff, 0x8c, 0x2e, 0x30, 0x74, 0x70, 0x55, 0xd9, 0xa4, 0xa7, 0xd0, 0x49, 0x76, 0x59, 0xdd, - 0xa7, 0x44, 0x05, 0xed, 0x04, 0xf8, 0xe0, 0x93, 0xcf, 0x01, 0x96, 0xfa, 0x46, 0xf8, 0xae, 0x27, - 0xf5, 0x2a, 0x58, 0x4e, 0xc7, 0x20, 0x3f, 0x49, 0xe5, 0x1b, 0x78, 0x42, 0xaa, 0x71, 0xf9, 0x5a, - 0xc9, 0x2c, 0xc7, 0x56, 0xc0, 0x42, 0xa0, 0x22, 0x7d, 0xa0, 0x38, 0x30, 0xf9, 0x15, 0xe3, 0xb9, - 0x8b, 0x5b, 0x2f, 0x5c, 0xdc, 0x3f, 0x12, 0x06, 0xb3, 0xa3, 0x22, 0x26, 0xaf, 0x61, 0xc0, 0x13, - 0xd3, 0x95, 0xaa, 0xf4, 0x94, 0xb2, 0x89, 0xa6, 0xac, 0xd4, 0x94, 0xd3, 0xe7, 0x39, 0x40, 0xd0, - 0xf7, 0x30, 0x72, 0xf0, 0x2a, 0xba, 0xc0, 0x07, 0x24, 0xbf, 0x97, 0x00, 0xfa, 0x15, 0x8c, 0x4b, - 0x91, 0x76, 0xdd, 0x86, 0x77, 0x30, 0x3e, 0x41, 0xce, 0x56, 0x37, 0xbb, 0xf7, 0x60, 0x96, 0x5b, - 0x4d, 0x93, 0x38, 0xdb, 0xc5, 0xdf, 0x81, 0x94, 0xc3, 0x88, 0x58, 0x79, 0x5c, 0x29, 0x94, 0x61, - 0x96, 0x38, 0xb5, 0x8b, 0x55, 0x35, 0x8a, 0x55, 0xbd, 0xfa, 0xbf, 0x09, 0xd6, 0x5b, 0xbc, 0x26, - 0xdf, 0x43, 0x2f, 0xff, 0x1e, 0x90, 0x84, 0xce, 0xd2, 0xd3, 0x32, 0x7b, 0xb4, 0x05, 0x15, 0x31, - 0xad, 0x29, 0xf7, 0xbc, 0xfa, 0x19, 0xf7, 0x92, 0x58, 0x1b, 0xf7, 0xb2, 0x4c, 0x26, 0xee, 0xf9, - 0xa7, 0xc0, 0xb8, 0x97, 0x1e, 0x10, 0xe3, 0x5e, 0x7e, 0x33, 0x68, 0x8d, 0x1c, 0xc3, 0xa0, 0xa8, - 0x4f, 0x64, 0x3f, 0x57, 0x68, 0x8e, 0xef, 0xd9, 0xe3, 0xad, 0x78, 0x1a, 0xa4, 0x28, 0x1f, 0x26, - 0x48, 0x45, 0xbc, 0x4c, 0x90, 0xaa, 0xd6, 0x24, 0x41, 0x8a, 0x2a, 0x61, 0x82, 0x54, 0x54, 0xc6, - 0x04, 0xa9, 0x4a, 0x0a, 0xad, 0x91, 0x37, 0xd0, 0xcf, 0x8b, 0x84, 0x30, 0x74, 0x94, 0xb4, 0xc4, - 0xd0, 0x51, 0x96, 0x13, 0x5a, 0x23, 0x2f, 0x01, 0x7e, 0x45, 0x69, 0x84, 0x81, 0x0c, 0xf5, 0xb1, - 0x5b, 0xd1, 0x98, 0x8d, 0x8a, 0x80, 0x76, 0xf9, 0x0e, 0xba, 0xb9, 0x45, 0x23, 0x9f, 0x65, 0xa1, - 0x6f, 0x17, 0x65, 0x36, 0xa9, 0x82, 0xda, 0xf7, 0x47, 0xe8, 0x17, 0x56, 0x81, 0x3c, 0x32, 0xab, - 0x58, 0x5c, 0xb4, 0xd9, 0xfe, 0x36, 0x38, 0x65, 0xad, 0x78, 0xa7, 0x0d, 0x6b, 0x95, 0x7d, 0x31, - 0xac, 0x55, 0x17, 0x80, 0xd6, 0x7e, 0x9e, 0x00, 0x59, 0x46, 0x97, 0xf3, 0x65, 0xc4, 0x31, 0x12, - 0x73, 0x1f, 0xaf, 0xd5, 0xd1, 0xd3, 0x96, 0xfe, 0xbf, 0xf7, 0xf5, 0xa7, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x49, 0x46, 0x0e, 0xa3, 0x00, 0x0a, 0x00, 0x00, + // 973 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0x6d, 0x6f, 0xdb, 0x36, + 0x10, 0x8e, 0xad, 0xd8, 0x91, 0xcf, 0xef, 0x6c, 0x9a, 0xaa, 0x2e, 0x06, 0x24, 0x2c, 0x06, 0xa4, + 0x18, 0x90, 0xac, 0x19, 0xb0, 0x01, 0x2b, 0xd6, 0x6e, 0x4b, 0xbb, 0x35, 0xc0, 0x36, 0x04, 0xda, + 0xdc, 0x8f, 0x13, 0x54, 0xeb, 0x9c, 0x10, 0x95, 0x25, 0x8d, 0xa4, 0x93, 0x74, 0x1f, 0xf7, 0xe7, + 0xf6, 0x23, 0xf6, 0x67, 0x06, 0x52, 0x94, 0x43, 0x49, 0x6e, 0x9d, 0x7e, 0xf3, 0x3d, 0xbc, 0x17, + 0xde, 0xa3, 0xbb, 0x87, 0x86, 0x7e, 0x98, 0xb1, 0xe3, 0x30, 0x63, 0x47, 0x19, 0x4f, 0x65, 0x4a, + 0x9c, 0x30, 0x63, 0xf4, 0x9f, 0x26, 0xb4, 0x4f, 0x63, 0x86, 0x89, 0x24, 0x03, 0x68, 0xb2, 0xc8, + 0x6b, 0xec, 0x37, 0x0e, 0x3b, 0x7e, 0x93, 0x45, 0x64, 0x0f, 0xda, 0x02, 0x67, 0x1c, 0xa5, 0xd7, + 0xd4, 0x98, 0xb1, 0xc8, 0x63, 0xe8, 0x73, 0x8c, 0x18, 0xc7, 0x99, 0x0c, 0x96, 0x9c, 0x09, 0xcf, + 0xd9, 0x77, 0x0e, 0x3b, 0x7e, 0xaf, 0x00, 0xa7, 0x9c, 0x09, 0xe5, 0x24, 0xf9, 0x52, 0x48, 0x8c, + 0x82, 0x0c, 0x91, 0x0b, 0x6f, 0x3b, 0x77, 0x32, 0xe0, 0xb9, 0xc2, 0x54, 0x85, 0x6c, 0xf9, 0x36, + 0x66, 0x33, 0xaf, 0xb5, 0xdf, 0x38, 0x74, 0x7d, 0x63, 0x11, 0x02, 0xdb, 0x49, 0xb8, 0x40, 0xaf, + 0xad, 0xeb, 0xea, 0xdf, 0xe4, 0x21, 0xb8, 0x71, 0x7a, 0x91, 0x06, 0x4b, 0x1e, 0x7b, 0x3b, 0x1a, + 0xdf, 0x51, 0xf6, 0x94, 0xc7, 0xe4, 0x05, 0x0c, 0x44, 0xb8, 0x88, 0x03, 0x96, 0x30, 0xc9, 0x42, + 0x89, 0x91, 0xe7, 0xee, 0x37, 0x0e, 0xbb, 0x27, 0xde, 0x91, 0x6a, 0xf6, 0xf7, 0x70, 0x11, 0x9f, + 0x15, 0x27, 0xa7, 0x69, 0x32, 0x67, 0x17, 0x7e, 0x5f, 0xd8, 0x20, 0x3d, 0x87, 0x7b, 0x6b, 0xbc, + 0xc8, 0x01, 0xf4, 0xec, 0x46, 0x0d, 0x35, 0x5d, 0xab, 0x4f, 0xcd, 0xd1, 0x2c, 0xcd, 0x50, 0x78, + 0x4d, 0xdd, 0x9f, 0xb1, 0xe8, 0xd7, 0x30, 0x3c, 0xe5, 0x18, 0x4a, 0xcc, 0xb9, 0xf5, 0xf1, 0x2f, + 0xf2, 0x18, 0xda, 0x33, 0x6d, 0xe8, 0x3c, 0xdd, 0x93, 0xae, 0xbe, 0x9d, 0x39, 0x37, 0x47, 0xf4, + 0x4f, 0x18, 0x95, 0xe3, 0x44, 0x46, 0x3e, 0x87, 0x41, 0x18, 0x73, 0x0c, 0xa3, 0xf7, 0x01, 0xde, + 0x30, 0x21, 0x85, 0x4e, 0xe0, 0xfa, 0x7d, 0x83, 0xbe, 0xd2, 0xa0, 0x95, 0xbf, 0xf9, 0xe1, 0xfc, + 0x07, 0x30, 0x7c, 0x89, 0x31, 0xda, 0xf7, 0xaa, 0x7c, 0x76, 0x7a, 0x0c, 0xa3, 0xb2, 0x8b, 0xc8, + 0xc8, 0x23, 0xe8, 0x24, 0xa9, 0x0c, 0xe6, 0xe9, 0x32, 0x89, 0x4c, 0x75, 0x37, 0x49, 0xe5, 0x4f, + 0xca, 0xa6, 0xff, 0x35, 0x60, 0x38, 0xcd, 0xa2, 0xf0, 0x23, 0x49, 0xeb, 0x33, 0xd3, 0xbc, 0xcb, + 0xcc, 0x38, 0x6b, 0x66, 0xa6, 0x98, 0x8d, 0xed, 0x0f, 0xcc, 0x46, 0x6b, 0xd3, 0x6c, 0xb4, 0x3f, + 0x6d, 0x36, 0x8e, 0x61, 0x54, 0x6e, 0x6e, 0x13, 0x1d, 0x0c, 0xdc, 0xf3, 0x50, 0x88, 0xeb, 0x94, + 0x47, 0x64, 0x17, 0x5a, 0xb8, 0x08, 0x59, 0x6c, 0x98, 0xc8, 0x0d, 0xd5, 0xc2, 0x65, 0x28, 0x2e, + 0xf5, 0x77, 0xea, 0xf9, 0xfa, 0x37, 0x99, 0x80, 0xbb, 0x14, 0xc8, 0x75, 0x6b, 0x8e, 0x76, 0x5e, + 0xd9, 0xe4, 0x01, 0xec, 0xa8, 0xdf, 0x01, 0x8b, 0x4c, 0xd7, 0x6d, 0x65, 0x9e, 0x45, 0xf4, 0x39, + 0x8c, 0xf3, 0x69, 0x29, 0x0a, 0x2a, 0xea, 0x9f, 0x80, 0x9b, 0x19, 0xd3, 0x4c, 0x5a, 0x5f, 0xf7, + 0xba, 0xf2, 0x59, 0x1d, 0xd3, 0x67, 0x40, 0xaa, 0xf1, 0x77, 0x9e, 0x37, 0x7a, 0x01, 0xe3, 0x9c, + 0x18, 0xbb, 0xf8, 0xfa, 0x86, 0x1f, 0x82, 0x9b, 0xe0, 0x75, 0x60, 0x35, 0xbd, 0x93, 0xe0, 0xf5, + 0x6b, 0xd5, 0xf7, 0x01, 0xf4, 0xd4, 0x51, 0xa5, 0xf7, 0x6e, 0x82, 0xd7, 0x53, 0x03, 0xd1, 0xa7, + 0x40, 0xaa, 0x85, 0x36, 0x7d, 0x83, 0x27, 0x30, 0xce, 0x67, 0x78, 0xe3, 0xdd, 0x54, 0xf6, 0xaa, + 0xeb, 0xa6, 0xec, 0x63, 0x18, 0xfe, 0xc2, 0x84, 0xb4, 0x72, 0xd3, 0x17, 0x30, 0x2a, 0x43, 0x22, + 0x23, 0x5f, 0x40, 0xa7, 0x60, 0x5a, 0x51, 0xe8, 0xd4, 0xbf, 0xc4, 0xed, 0x39, 0xed, 0x01, 0xbc, + 0x41, 0x2e, 0x58, 0x9a, 0xa8, 0x74, 0xdf, 0x40, 0x77, 0x65, 0x89, 0x2c, 0x57, 0x62, 0x7e, 0x85, + 0xdc, 0x5c, 0xdd, 0x58, 0x64, 0x04, 0x4a, 0xc3, 0x35, 0xa5, 0x2d, 0x5f, 0xcb, 0xf9, 0xdf, 0x30, + 0xf4, 0x71, 0xce, 0x51, 0x5c, 0xfe, 0x91, 0xbe, 0xc3, 0xc4, 0xc7, 0x79, 0x6d, 0x15, 0x1f, 0x41, + 0x27, 0x17, 0x03, 0x35, 0x4f, 0xb9, 0xb2, 0xbb, 0x39, 0x70, 0x16, 0x91, 0xcf, 0x00, 0x66, 0x7a, + 0x22, 0xa2, 0x20, 0x94, 0x7a, 0x97, 0x1c, 0xbf, 0x63, 0x90, 0x1f, 0xa4, 0x8a, 0x8d, 0x43, 0x21, + 0xd5, 0xe7, 0xca, 0x17, 0xc9, 0xf1, 0x5d, 0x05, 0x4c, 0x05, 0x2a, 0xd2, 0x07, 0x8a, 0x03, 0x53, + 0x5f, 0x31, 0x6e, 0x0d, 0x6e, 0xa3, 0x34, 0xb8, 0xbf, 0xe5, 0x0c, 0xae, 0x5c, 0x45, 0x46, 0x9e, + 0xc1, 0x80, 0xe7, 0x66, 0x20, 0xd5, 0xd5, 0x0b, 0xca, 0x76, 0x35, 0x65, 0x95, 0xa6, 0xfc, 0x3e, + 0xb7, 0x00, 0x41, 0x5f, 0xc3, 0xc8, 0xc7, 0xab, 0xf4, 0x1d, 0xde, 0xa1, 0xf8, 0x47, 0x09, 0xa0, + 0x5f, 0xc2, 0xb8, 0x92, 0x69, 0xd3, 0x34, 0xbc, 0x82, 0xf1, 0x1b, 0xe4, 0x6c, 0xfe, 0x7e, 0xf3, + 0x1e, 0x4c, 0xac, 0xd5, 0x34, 0x85, 0x57, 0xbb, 0xf8, 0x2b, 0x90, 0x6a, 0x1a, 0x91, 0xa9, 0x88, + 0x2b, 0x85, 0x32, 0x5c, 0x15, 0x2e, 0xec, 0xf2, 0xad, 0x9a, 0xe5, 0x5b, 0x9d, 0xfc, 0xdb, 0x02, + 0xe7, 0x25, 0xde, 0x90, 0xef, 0xa0, 0x67, 0x3f, 0x28, 0x24, 0xa7, 0xb3, 0xf2, 0x36, 0x4d, 0xee, + 0xaf, 0x41, 0x45, 0x46, 0xb7, 0x54, 0xb8, 0xad, 0x7e, 0x26, 0xbc, 0xa2, 0xf6, 0x26, 0xbc, 0x2a, + 0x93, 0x79, 0xb8, 0xfd, 0x96, 0x98, 0xf0, 0xca, 0x0b, 0x64, 0xc2, 0xab, 0x8f, 0x0e, 0xdd, 0x22, + 0xa7, 0x30, 0x28, 0xeb, 0x13, 0xd9, 0xb3, 0x2e, 0x6a, 0xf1, 0x3d, 0x79, 0xb0, 0x16, 0x2f, 0x92, + 0x94, 0xe5, 0xc3, 0x24, 0xa9, 0x89, 0x97, 0x49, 0x52, 0xd7, 0x9a, 0x3c, 0x49, 0x59, 0x25, 0x4c, + 0x92, 0x9a, 0xca, 0x98, 0x24, 0x75, 0x49, 0xa1, 0x5b, 0xe4, 0x39, 0xf4, 0x6d, 0x91, 0x10, 0x86, + 0x8e, 0x8a, 0x96, 0x18, 0x3a, 0xaa, 0x72, 0x42, 0xb7, 0xc8, 0x53, 0x80, 0x9f, 0x51, 0x1a, 0x61, + 0x20, 0x43, 0xed, 0x76, 0x2b, 0x1a, 0x93, 0x51, 0x19, 0xd0, 0x21, 0xdf, 0x42, 0xd7, 0x5a, 0x34, + 0x72, 0x6f, 0x95, 0xfa, 0x76, 0x51, 0x26, 0xbb, 0x75, 0x50, 0xc7, 0x7e, 0x0f, 0xfd, 0xd2, 0x2a, + 0x90, 0xfb, 0x66, 0x15, 0xcb, 0x8b, 0x36, 0xd9, 0x5b, 0x07, 0x17, 0xac, 0x95, 0x67, 0xda, 0xb0, + 0x56, 0xdb, 0x17, 0xc3, 0x5a, 0x7d, 0x01, 0xe8, 0xd6, 0x8f, 0xbb, 0x40, 0x66, 0xe9, 0xe2, 0x68, + 0x96, 0x72, 0x4c, 0xc5, 0x51, 0x84, 0x37, 0xca, 0xf5, 0x6d, 0x5b, 0xff, 0x87, 0xfd, 0xea, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xd9, 0x04, 0xd7, 0x1e, 0xd4, 0x0a, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/api/api.proto b/api/api.proto index 5d9ce1a1e3..cec283d695 100644 --- a/api/api.proto +++ b/api/api.proto @@ -12,6 +12,13 @@ message Client { bool public = 5; string name = 6; string logo_url = 7; + SamlInitiatedConfig saml_initiated = 8; +} + +// SamlInitiatedConfig provides config options for SAML initiated login on a client +message SamlInitiatedConfig { + string redirect_uri = 1; + repeated string scopes =2; } // CreateClientReq is a request to make a client. @@ -22,7 +29,7 @@ message CreateClientReq { // CreateClientResp returns the response from creating a client. message CreateClientResp { bool already_exists = 1; - Client client = 2; + Client client = 2; } // DeleteClientReq is a request to delete a client. @@ -31,7 +38,7 @@ message DeleteClientReq { string id = 1; } -// DeleteClientResp determines if the client is deleted successfully. +// DeleteClientResp determines if the client is deleted successfully. message DeleteClientResp { bool not_found = 1; } @@ -43,6 +50,7 @@ message UpdateClientReq { repeated string trusted_peers = 3; string name = 4; string logo_url = 5; + SamlInitiatedConfig saml_initiated = 6; } // UpdateClientResp returns the reponse form updating a client. @@ -80,7 +88,7 @@ message UpdatePasswordReq { string new_username = 3; } -// UpdatePasswordResp returns the response from modifying an existing password. +// UpdatePasswordResp returns the response from modifying an existing password. message UpdatePasswordResp { bool not_found = 1; } @@ -90,7 +98,7 @@ message DeletePasswordReq { string email = 1; } -// DeletePasswordResp returns the response from deleting a password. +// DeletePasswordResp returns the response from deleting a password. message DeletePasswordResp { bool not_found = 1; } @@ -142,7 +150,7 @@ message RevokeRefreshReq { string client_id = 2; } -// RevokeRefreshResp determines if the refresh token is revoked successfully. +// RevokeRefreshResp determines if the refresh token is revoked successfully. message RevokeRefreshResp { // Set to true is refresh token was not found and token could not be revoked. bool not_found = 1; diff --git a/server/api.go b/server/api.go index 55784b9020..55cbe223a4 100644 --- a/server/api.go +++ b/server/api.go @@ -63,6 +63,10 @@ func (d dexAPI) CreateClient(ctx context.Context, req *api.CreateClientReq) (*ap Name: req.Client.Name, LogoURL: req.Client.LogoUrl, } + if req.Client.SamlInitiated != nil { + c.SAMLInitiated.RedirectURI = req.Client.SamlInitiated.RedirectUri + c.SAMLInitiated.Scopes = req.Client.SamlInitiated.Scopes + } if err := d.s.CreateClient(c); err != nil { if err == storage.ErrAlreadyExists { return &api.CreateClientResp{AlreadyExists: true}, nil @@ -94,6 +98,14 @@ func (d dexAPI) UpdateClient(ctx context.Context, req *api.UpdateClientReq) (*ap if req.LogoUrl != "" { old.LogoURL = req.LogoUrl } + if req.SamlInitiated != nil { + if req.SamlInitiated.RedirectUri != "" { + old.SAMLInitiated.RedirectURI = req.SamlInitiated.RedirectUri + } + if len(req.SamlInitiated.Scopes) > 0 { + old.SAMLInitiated.Scopes = req.SamlInitiated.Scopes + } + } return old, nil }) diff --git a/storage/kubernetes/types.go b/storage/kubernetes/types.go index 5eda178111..acb14945f1 100644 --- a/storage/kubernetes/types.go +++ b/storage/kubernetes/types.go @@ -167,6 +167,14 @@ type Client struct { Name string `json:"name,omitempty"` LogoURL string `json:"logoURL,omitempty"` + + SAMLInitiated SAMLInitiatedConfig `json:"samlInitiated"` +} + +// SAMLInitiatedConfig is mirrored from storage.SAMLInitiatedConfig +type SAMLInitiatedConfig struct { + RedirectURI string `json:"redirectURI"` + Scopes []string `json:"scopes"` } // ClientList is a list of Clients. @@ -193,6 +201,10 @@ func (cli *client) fromStorageClient(c storage.Client) Client { Public: c.Public, Name: c.Name, LogoURL: c.LogoURL, + SAMLInitiated: SAMLInitiatedConfig{ + RedirectURI: c.SAMLInitiated.RedirectURI, + Scopes: c.SAMLInitiated.Scopes, + }, } } @@ -205,6 +217,10 @@ func toStorageClient(c Client) storage.Client { Public: c.Public, Name: c.Name, LogoURL: c.LogoURL, + SAMLInitiated: storage.SAMLInitiatedConfig{ + RedirectURI: c.SAMLInitiated.RedirectURI, + Scopes: c.SAMLInitiated.Scopes, + }, } } diff --git a/storage/sql/crud.go b/storage/sql/crud.go index e87dc56aa5..11257205d6 100644 --- a/storage/sql/crud.go +++ b/storage/sql/crud.go @@ -477,8 +477,12 @@ func (c *conn) UpdateClient(id string, updater func(old storage.Client) (storage public = $4, name = $5, logo_url = $6 - where id = $7; - `, nc.Secret, encoder(nc.RedirectURIs), encoder(nc.TrustedPeers), nc.Public, nc.Name, nc.LogoURL, id, + saml_init_redirect_uri = $7, + saml_init_scopes = $8 + where id = $9; + `, + nc.Secret, encoder(nc.RedirectURIs), encoder(nc.TrustedPeers), nc.Public, nc.Name, + nc.LogoURL, nc.SAMLInitiated.RedirectURI, encoder(nc.SAMLInitiated.Scopes), id, ) if err != nil { return fmt.Errorf("update client: %v", err) @@ -490,12 +494,13 @@ func (c *conn) UpdateClient(id string, updater func(old storage.Client) (storage func (c *conn) CreateClient(cli storage.Client) error { _, err := c.Exec(` insert into client ( - id, secret, redirect_uris, trusted_peers, public, name, logo_url + id, secret, redirect_uris, trusted_peers, public, name, logo_url, + saml_init_redirect_uri, saml_init_scopes ) - values ($1, $2, $3, $4, $5, $6, $7); + values ($1, $2, $3, $4, $5, $6, $7, $8, $9); `, - cli.ID, cli.Secret, encoder(cli.RedirectURIs), encoder(cli.TrustedPeers), - cli.Public, cli.Name, cli.LogoURL, + cli.ID, cli.Secret, encoder(cli.RedirectURIs), encoder(cli.TrustedPeers), cli.Public, + cli.Name, cli.LogoURL, cli.SAMLInitiated.RedirectURI, encoder(cli.SAMLInitiated.Scopes), ) if err != nil { if c.alreadyExistsCheck(err) { @@ -509,7 +514,8 @@ func (c *conn) CreateClient(cli storage.Client) error { func getClient(q querier, id string) (storage.Client, error) { return scanClient(q.QueryRow(` select - id, secret, redirect_uris, trusted_peers, public, name, logo_url + id, secret, redirect_uris, trusted_peers, public, name, logo_url, + saml_init_redirect_uri, saml_init_scopes from client where id = $1; `, id)) } @@ -521,7 +527,8 @@ func (c *conn) GetClient(id string) (storage.Client, error) { func (c *conn) ListClients() ([]storage.Client, error) { rows, err := c.Query(` select - id, secret, redirect_uris, trusted_peers, public, name, logo_url + id, secret, redirect_uris, trusted_peers, public, name, logo_url, + saml_init_redirect_uri, saml_init_scopes from client; `) if err != nil { @@ -543,8 +550,8 @@ func (c *conn) ListClients() ([]storage.Client, error) { func scanClient(s scanner) (cli storage.Client, err error) { err = s.Scan( - &cli.ID, &cli.Secret, decoder(&cli.RedirectURIs), decoder(&cli.TrustedPeers), - &cli.Public, &cli.Name, &cli.LogoURL, + &cli.ID, &cli.Secret, decoder(&cli.RedirectURIs), decoder(&cli.TrustedPeers), &cli.Public, + &cli.Name, &cli.LogoURL, &cli.SAMLInitiated.RedirectURI, decoder(&cli.SAMLInitiated.Scopes), ) if err != nil { if err == sql.ErrNoRows { diff --git a/storage/sql/migrate.go b/storage/sql/migrate.go index dc727535d4..0ae222350b 100644 --- a/storage/sql/migrate.go +++ b/storage/sql/migrate.go @@ -229,4 +229,13 @@ var migrations = []migration{ }, flavor: &flavorMySQL, }, + { + stmts: []string{` + alter table client + add column saml_init_redirect_uri text not null default '';`, + ` + alter table client + add column saml_init_scopes bytea not null; -- JSON array of strings`, + }, + }, } From 372f03b7954fba361bd906947d2245367ca73536 Mon Sep 17 00:00:00 2001 From: Scott Reisor Date: Tue, 20 Aug 2019 10:23:32 -0400 Subject: [PATCH 4/8] update conformance test, fix sql storage --- storage/conformance/conformance.go | 8 ++++++++ storage/sql/crud.go | 2 +- storage/sql/migrate.go | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/storage/conformance/conformance.go b/storage/conformance/conformance.go index 8a55a75ba3..1cacc518e3 100644 --- a/storage/conformance/conformance.go +++ b/storage/conformance/conformance.go @@ -246,6 +246,10 @@ func testClientCRUD(t *testing.T, s storage.Storage) { RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"}, Name: "dex client", LogoURL: "https://goo.gl/JIyzIC", + SAMLInitiated: storage.SAMLInitiatedConfig{ + RedirectURI: "foo://bar.com/", + Scopes: []string{"idtoken", "profile", "email"}, + }, } err := s.DeleteClient(id1) mustBeErrNotFound(t, "client", err) @@ -265,6 +269,10 @@ func testClientCRUD(t *testing.T, s storage.Storage) { RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"}, Name: "dex client", LogoURL: "https://goo.gl/JIyzIC", + SAMLInitiated: storage.SAMLInitiatedConfig{ + RedirectURI: "foo://bar.com/", + Scopes: []string{"idtoken", "profile", "email"}, + }, } if err := s.CreateClient(c2); err != nil { diff --git a/storage/sql/crud.go b/storage/sql/crud.go index 11257205d6..a797abd759 100644 --- a/storage/sql/crud.go +++ b/storage/sql/crud.go @@ -476,7 +476,7 @@ func (c *conn) UpdateClient(id string, updater func(old storage.Client) (storage trusted_peers = $3, public = $4, name = $5, - logo_url = $6 + logo_url = $6, saml_init_redirect_uri = $7, saml_init_scopes = $8 where id = $9; diff --git a/storage/sql/migrate.go b/storage/sql/migrate.go index 0ae222350b..eb8260b2e8 100644 --- a/storage/sql/migrate.go +++ b/storage/sql/migrate.go @@ -235,7 +235,7 @@ var migrations = []migration{ add column saml_init_redirect_uri text not null default '';`, ` alter table client - add column saml_init_scopes bytea not null; -- JSON array of strings`, + add column saml_init_scopes bytea; -- JSON array of strings`, }, }, } From 21ab7634f40f3e9a017357f0a27483e392a8abca Mon Sep 17 00:00:00 2001 From: Scott Reisor Date: Tue, 20 Aug 2019 12:10:02 -0400 Subject: [PATCH 5/8] update api test --- server/api_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/server/api_test.go b/server/api_test.go index 511d5d39db..df1a499d74 100644 --- a/server/api_test.go +++ b/server/api_test.go @@ -408,6 +408,10 @@ func TestUpdateClient(t *testing.T) { TrustedPeers: []string{"test"}, Name: "test", LogoUrl: "https://logout", + SamlInitiated: &api.SamlInitiatedConfig{ + RedirectUri: "https://redirect", + Scopes: []string{"idtoken"}, + }, }, wantErr: false, want: &api.UpdateClientResp{ @@ -489,6 +493,17 @@ func TestUpdateClient(t *testing.T) { t.Errorf("expected trusted peer: %s", peer) } } + if tc.req.SamlInitiated != nil { + if tc.req.SamlInitiated.RedirectUri != client.SAMLInitiated.RedirectURI { + t.Errorf("expected stored client with SAML initiated redirectURI: %s, found %s", tc.req.SamlInitiated.RedirectUri, client.SAMLInitiated.RedirectURI) + } + for _, scope := range tc.req.SamlInitiated.Scopes { + found := find(scope, client.SAMLInitiated.Scopes) + if !found { + t.Errorf("expected SAML initiated scope: %s", scope) + } + } + } } if tc.cleanup != nil { From 104d398648c17d17f1a29671a6054dbb33ad83e5 Mon Sep 17 00:00:00 2001 From: Scott Reisor Date: Tue, 24 Sep 2019 16:42:50 -0400 Subject: [PATCH 6/8] small changes from review --- server/handlers.go | 4 ++-- storage/kubernetes/types.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/handlers.go b/server/handlers.go index e956150549..da1624e549 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -511,8 +511,8 @@ func (s *Server) handleSAMLCallback(r *http.Request) (string, string, int, error s.logger.Warnf("SAML IdP initiated login attempt made to client %q, but support for this flow is not configured. Update the client config to support this login flow.", relayState) return "", "", http.StatusBadRequest, errors.New("user session error") } - // TODO: should we check that 'idtoken' is one of the scopes? or just put the - // burden on the user to confiugre these scopes correctly? + // TODO: should we check that 'openid' is one of the scopes? or just put the + // burden on the user to configure these scopes correctly? scopes := client.SAMLInitiated.Scopes if len(scopes) == 0 { scopes = saml.DefaultIDPInitiatedScopes diff --git a/storage/kubernetes/types.go b/storage/kubernetes/types.go index acb14945f1..d0760481fd 100644 --- a/storage/kubernetes/types.go +++ b/storage/kubernetes/types.go @@ -168,13 +168,13 @@ type Client struct { Name string `json:"name,omitempty"` LogoURL string `json:"logoURL,omitempty"` - SAMLInitiated SAMLInitiatedConfig `json:"samlInitiated"` + SAMLInitiated SAMLInitiatedConfig `json:"samlInitiated,omitempty"` } // SAMLInitiatedConfig is mirrored from storage.SAMLInitiatedConfig type SAMLInitiatedConfig struct { RedirectURI string `json:"redirectURI"` - Scopes []string `json:"scopes"` + Scopes []string `json:"scopes,omitempty"` } // ClientList is a list of Clients. From 4a178b8488b9a307ba0c6e38f613090a1657ccf0 Mon Sep 17 00:00:00 2001 From: Scott Reisor Date: Tue, 24 Sep 2019 16:43:35 -0400 Subject: [PATCH 7/8] add documentation for SAML IdP initiated flow --- Documentation/connectors/saml.md | 43 ++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/Documentation/connectors/saml.md b/Documentation/connectors/saml.md index c9af0995e6..be29d394f7 100644 --- a/Documentation/connectors/saml.md +++ b/Documentation/connectors/saml.md @@ -6,8 +6,6 @@ The SAML provider allows authentication through the SAML 2.0 HTTP POST binding. The connector uses the value of the `NameID` element as the user's unique identifier which dex assumes is both unique and never changes. Use the `nameIDPolicyFormat` to ensure this is set to a value which satisfies these requirements. -Unlike some clients which will process unprompted AuthnResponses, dex must send the initial AuthnRequest and validates the response's InResponseTo value. - ## Caveats __The connector doesn't support refresh tokens__ since the SAML 2.0 protocol doesn't provide a way to requery a provider without interaction. If the "offline_access" scope is requested, it will be ignored. @@ -111,3 +109,44 @@ connectors: emailAttr: email groupsAttr: groups ``` + +## Identity Provider Initiated Login + +The SAML connector can support the identity provider (IdP) initiated flow with some special configuration. This flow allows a SAML user to initiate the login from a portal on their provider without needing to redirect back to the provider for further authentication. + +Because this flow does not follow the normal OAuth flow (App -> Auth Provider -> App), dex requires some configuration on the client to know how to handle an unprompted AuthnResponse. For each client that you would like to support SAML's IdP initiated flow, you must configure the `samlInitiated` section. + +As a static client: + +```yaml +staticClients: +- id: example-app + name: "Example App" + secret: ZXhhbXBsZS1hcHAtc2VjcmV0 + redirectURIs: + - http://127.0.0.1:5555/callback + samlInitiated: + redirecURI: http://127.0.0.1:5555/callback + scopes: ["openid", "profile", "email"] +``` + +Or via the API: + +```golang +client := &api.Client{ + Id: "example-app", + Name: "Example App", + Secret: "ZXhhbXBsZS1hcHAtc2VjcmV0", + RedirectUris: []string{"http://127.0.0.1:5555/callback"}, + SamlInitiated: &api.SamlInitiatedConfig{ + RedirectURI: "http://127.0.0.1:5555/callback", + Scopes: []string{"openid", "profile", "email"} + }, +} +``` + +The scopes above are optional. If they are not provided the default set `openid profile email groups` will be used. However, without `samlInitiated.redirecURI` dex will reject any IdP initiated login for that client. + +When configuring an application on your SAML provider, you must provide the `client_id` as the Default Relay Sate. This is how dex will match the AuthnResponse to the client, decide what claims to build, and where to redirect the user after handling the response. + +After handling the AuthnResponse dex will redirect back to your application with a `code` and an empty `state`. Your application must be willing to accept the empty `state` and redeem the `code` for user tokens. The implicit flow is not supported since there is no way of generating a verifiable `nonce` in this setup. From 8738800622c4abbf83789a5246a7ddd685539cb2 Mon Sep 17 00:00:00 2001 From: Scott Reisor Date: Wed, 25 Mar 2020 16:09:22 -0400 Subject: [PATCH 8/8] fix renderError call --- server/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/handlers.go b/server/handlers.go index da1624e549..1411a79eec 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -408,7 +408,7 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) var err error authID, samlInResponseTo, code, err = s.handleSAMLCallback(r) if err != nil { - s.renderError(w, code, fmt.Sprintf("Error processing SAML callback: %s.", err)) + s.renderError(r, w, code, fmt.Sprintf("Error processing SAML callback: %s.", err)) return } default: