@ -135,7 +135,6 @@ func (c *Config) openConnector(logger logrus.FieldLogger) (interface {
requiredFields := [ ] struct {
requiredFields := [ ] struct {
name , val string
name , val string
} {
} {
{ "issuer" , c . Issuer } ,
{ "ssoURL" , c . SSOURL } ,
{ "ssoURL" , c . SSOURL } ,
{ "usernameAttr" , c . UsernameAttr } ,
{ "usernameAttr" , c . UsernameAttr } ,
{ "emailAttr" , c . EmailAttr } ,
{ "emailAttr" , c . EmailAttr } ,
@ -240,7 +239,7 @@ type provider struct {
logger logrus . FieldLogger
logger logrus . FieldLogger
}
}
func ( p * provider ) POSTData ( s connector . Scopes ) ( action , value string , err error ) {
func ( p * provider ) POSTData ( s connector . Scopes , id string ) ( action , value string , err error ) {
// NOTE(ericchiang): If we can't follow up with the identity provider, can we
// NOTE(ericchiang): If we can't follow up with the identity provider, can we
// support refresh tokens?
// support refresh tokens?
@ -250,28 +249,32 @@ func (p *provider) POSTData(s connector.Scopes) (action, value string, err error
r := & authnRequest {
r := & authnRequest {
ProtocolBinding : bindingPOST ,
ProtocolBinding : bindingPOST ,
ID : "_" + uuidv4 ( ) ,
ID : id ,
IssueInstant : xmlTime ( p . now ( ) ) ,
IssueInstant : xmlTime ( p . now ( ) ) ,
Destination : p . ssoURL ,
Destination : p . ssoURL ,
Issuer : & issuer {
Issuer : p . issuer ,
} ,
NameIDPolicy : & nameIDPolicy {
NameIDPolicy : & nameIDPolicy {
AllowCreate : true ,
AllowCreate : true ,
Format : p . nameIDPolicyFormat ,
Format : p . nameIDPolicyFormat ,
} ,
} ,
AssertionConsumerServiceURL : p . redirectURI ,
AssertionConsumerServiceURL : p . redirectURI ,
}
}
if p . issuer != "" {
// Issuer for the request is optional. For example, okta always ignores
// this value.
r . Issuer = & issuer { Issuer : p . issuer }
}
data , err := xml . MarshalIndent ( r , "" , " " )
data , err := xml . MarshalIndent ( r , "" , " " )
if err != nil {
if err != nil {
return "" , "" , fmt . Errorf ( "marshal authn request: %v" , err )
return "" , "" , fmt . Errorf ( "marshal authn request: %v" , err )
}
}
// See: https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
// "3.5.4 Message Encoding"
return p . ssoURL , base64 . StdEncoding . EncodeToString ( data ) , nil
return p . ssoURL , base64 . StdEncoding . EncodeToString ( data ) , nil
}
}
func ( p * provider ) HandlePOST ( s connector . Scopes , samlResponse string ) ( ident connector . Identity , err error ) {
func ( p * provider ) HandlePOST ( s connector . Scopes , samlResponse , inResponseTo string ) ( ident connector . Identity , err error ) {
rawResp , err := base64 . StdEncoding . DecodeString ( samlResponse )
rawResp , err := base64 . StdEncoding . DecodeString ( samlResponse )
if err != nil {
if err != nil {
return ident , fmt . Errorf ( "decode response: %v" , err )
return ident , fmt . Errorf ( "decode response: %v" , err )
@ -287,6 +290,17 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse string) (ident co
return ident , fmt . Errorf ( "unmarshal response: %v" , err )
return ident , fmt . Errorf ( "unmarshal response: %v" , err )
}
}
if p . issuer != "" && resp . Issuer != nil && resp . Issuer . Issuer != p . issuer {
return ident , fmt . Errorf ( "expected Issuer value %s, got %s" , p . issuer , resp . Issuer . Issuer )
}
// Verify InResponseTo value matches the expected ID associated with
// the RelayState.
if resp . InResponseTo != inResponseTo {
return ident , fmt . Errorf ( "expected InResponseTo value %s, got %s" , inResponseTo , resp . InResponseTo )
}
// Destination is optional.
if resp . Destination != "" && resp . Destination != p . redirectURI {
if resp . Destination != "" && resp . Destination != p . redirectURI {
return ident , fmt . Errorf ( "expected destination %q got %q" , p . redirectURI , resp . Destination )
return ident , fmt . Errorf ( "expected destination %q got %q" , p . redirectURI , resp . Destination )
@ -327,26 +341,26 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse string) (ident co
}
}
if ident . Email , _ = attributes . get ( p . emailAttr ) ; ident . Email == "" {
if ident . Email , _ = attributes . get ( p . emailAttr ) ; ident . Email == "" {
return ident , fmt . Errorf ( "no attribute with name %q" , p . emailAttr )
return ident , fmt . Errorf ( "no attribute with name %q: %s " , p . emailAttr , attributes . names ( ) )
}
}
ident . EmailVerified = true
ident . EmailVerified = true
if ident . Username , _ = attributes . get ( p . usernameAttr ) ; ident . Username == "" {
if ident . Username , _ = attributes . get ( p . usernameAttr ) ; ident . Username == "" {
return ident , fmt . Errorf ( "no attribute with name %q" , p . usernameAttr )
return ident , fmt . Errorf ( "no attribute with name %q: %s " , p . usernameAttr , attributes . names ( ) )
}
}
if s . Groups && p . groupsAttr != "" {
if s . Groups && p . groupsAttr != "" {
if p . groupsDelim != "" {
if p . groupsDelim != "" {
groupsStr , ok := attributes . get ( p . groupsAttr )
groupsStr , ok := attributes . get ( p . groupsAttr )
if ! ok {
if ! ok {
return ident , fmt . Errorf ( "no attribute with name %q" , p . groupsAttr )
return ident , fmt . Errorf ( "no attribute with name %q: %s " , p . groupsAttr , attributes . names ( ) )
}
}
// TODO(ericchiang): Do we need to further trim whitespace?
// TODO(ericchiang): Do we need to further trim whitespace?
ident . Groups = strings . Split ( groupsStr , p . groupsDelim )
ident . Groups = strings . Split ( groupsStr , p . groupsDelim )
} else {
} else {
groups , ok := attributes . all ( p . groupsAttr )
groups , ok := attributes . all ( p . groupsAttr )
if ! ok {
if ! ok {
return ident , fmt . Errorf ( "no attribute with name %q" , p . groupsAttr )
return ident , fmt . Errorf ( "no attribute with name %q: %s " , p . groupsAttr , attributes . names ( ) )
}
}
ident . Groups = groups
ident . Groups = groups
}
}
@ -427,6 +441,9 @@ func (p *provider) validateSubjectConfirmation(subject *subject) error {
}
}
// Validates the Conditions element and all of it's content
// Validates the Conditions element and all of it's content
//
// See: https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
// "2.3.3 Element <Assertion>"
func ( p * provider ) validateConditions ( assertion * assertion ) error {
func ( p * provider ) validateConditions ( assertion * assertion ) error {
// Checks if a Conditions element exists
// Checks if a Conditions element exists
conditions := assertion . Conditions
conditions := assertion . Conditions
@ -452,15 +469,17 @@ func (p *provider) validateConditions(assertion *assertion) error {
if audienceRestriction != nil {
if audienceRestriction != nil {
audiences := audienceRestriction . Audiences
audiences := audienceRestriction . Audiences
if audiences != nil && len ( audiences ) > 0 {
if audiences != nil && len ( audiences ) > 0 {
values := make ( [ ] string , len ( audiences ) )
issuerInAudiences := false
issuerInAudiences := false
for _ , audience := range audiences {
for i , audience := range audiences {
if audience . Value == p . issuer {
if audience . Value == p . redirectURI {
issuerInAudiences = true
issuerInAudiences = true
break
break
}
}
values [ i ] = audience . Value
}
}
if ! issuerInAudiences {
if ! issuerInAudiences {
return fmt . Errorf ( "required audience %s was not in Response audiences %s" , p . issuer , audienc es)
return fmt . Errorf ( "required audience %s was not in Response audiences %s" , p . redirectURI , valu es)
}
}
}
}
}
}