github.com/greenpau/go-authcrunch@v1.1.4/pkg/idp/saml/provider.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package saml 16 17 import ( 18 "context" 19 "encoding/json" 20 "encoding/xml" 21 samllib "github.com/crewjam/saml" 22 "github.com/crewjam/saml/samlsp" 23 "github.com/greenpau/go-authcrunch/pkg/authn/enums/operator" 24 "github.com/greenpau/go-authcrunch/pkg/authn/icons" 25 "github.com/greenpau/go-authcrunch/pkg/errors" 26 "github.com/greenpau/go-authcrunch/pkg/requests" 27 fileutil "github.com/greenpau/go-authcrunch/pkg/util/file" 28 "go.uber.org/zap" 29 "io/ioutil" 30 "net/http" 31 "net/url" 32 "strings" 33 ) 34 35 const ( 36 providerKind = "saml" 37 ) 38 39 // IdentityProvider represents SAML-based identity provider. 40 type IdentityProvider struct { 41 config *Config `json:"config,omitempty" xml:"config,omitempty" yaml:"config,omitempty"` 42 serviceProviders map[string]*samllib.ServiceProvider 43 idpMetadataURL *url.URL 44 // loginURL is the link to Azure AD authentication portal. 45 // The link is auto-generated based on Azure AD tenant and 46 // application IDs. 47 loginURL string 48 logger *zap.Logger 49 configured bool 50 } 51 52 // NewIdentityProvider return an instance of IdentityProvider. 53 func NewIdentityProvider(cfg *Config, logger *zap.Logger) (*IdentityProvider, error) { 54 if logger == nil { 55 return nil, errors.ErrIdentityProviderConfigureLoggerNotFound 56 } 57 58 b := &IdentityProvider{ 59 config: cfg, 60 logger: logger, 61 } 62 63 if err := b.config.Validate(); err != nil { 64 return nil, err 65 } 66 67 return b, nil 68 } 69 70 // GetRealm return authentication realm. 71 func (b *IdentityProvider) GetRealm() string { 72 return b.config.Realm 73 } 74 75 // GetName return the name associated with this identity provider. 76 func (b *IdentityProvider) GetName() string { 77 return b.config.Name 78 } 79 80 // GetKind returns the authentication method associated with this identity provider. 81 func (b *IdentityProvider) GetKind() string { 82 return providerKind 83 } 84 85 // Configured returns true if the identity provider was configured. 86 func (b *IdentityProvider) Configured() bool { 87 return b.configured 88 } 89 90 // Request performs the requested identity provider operation. 91 func (b *IdentityProvider) Request(op operator.Type, r *requests.Request) error { 92 switch op { 93 case operator.Authenticate: 94 return b.Authenticate(r) 95 } 96 return errors.ErrOperatorNotSupported.WithArgs(op) 97 } 98 99 // GetConfig returns IdentityProvider configuration. 100 func (b *IdentityProvider) GetConfig() map[string]interface{} { 101 var m map[string]interface{} 102 j, _ := json.Marshal(b.config) 103 json.Unmarshal(j, &m) 104 return m 105 } 106 107 // Configure configures IdentityProvider. 108 func (b *IdentityProvider) Configure() error { 109 b.loginURL = b.config.IdpLoginURL 110 111 idpSignCert, err := fileutil.ReadCertFile(b.config.IdpSignCertLocation) 112 if err != nil { 113 return err 114 } 115 116 // Obtain SAML IdP Metadata 117 opts := samlsp.Options{} 118 if strings.HasPrefix(b.config.IdpMetadataLocation, "http") { 119 idpMetadataURL, err := url.Parse(b.config.IdpMetadataLocation) 120 if err != nil { 121 return err 122 } 123 b.idpMetadataURL = idpMetadataURL 124 opts.URL = *idpMetadataURL 125 idpMetadata, err := samlsp.FetchMetadata( 126 context.Background(), 127 http.DefaultClient, 128 *idpMetadataURL, 129 ) 130 if err != nil { 131 return err 132 } 133 opts.IDPMetadata = idpMetadata 134 } else { 135 metadataFileContent, err := ioutil.ReadFile(b.config.IdpMetadataLocation) 136 if err != nil { 137 return err 138 } 139 idpMetadata, err := samlsp.ParseMetadata(metadataFileContent) 140 if err != nil { 141 return err 142 } 143 opts.IDPMetadata = idpMetadata 144 } 145 146 b.serviceProviders = make(map[string]*samllib.ServiceProvider) 147 for _, acsURL := range b.config.AssertionConsumerServiceURLs { 148 sp := samlsp.DefaultServiceProvider(opts) 149 sp.AllowIDPInitiated = true 150 //sp.EntityID = sp.IDPMetadata.EntityID 151 152 cfgAcsURL, _ := url.Parse(acsURL) 153 sp.AcsURL = *cfgAcsURL 154 155 entityID, _ := url.Parse(b.config.EntityID) 156 sp.MetadataURL = *entityID 157 158 if b.idpMetadataURL != nil { 159 sp.MetadataURL = *b.idpMetadataURL 160 } 161 162 for i := range sp.IDPMetadata.IDPSSODescriptors { 163 idpSSODescriptor := &sp.IDPMetadata.IDPSSODescriptors[i] 164 keyDescriptor := &samllib.KeyDescriptor{ 165 Use: "signing", 166 KeyInfo: samllib.KeyInfo{ 167 XMLName: xml.Name{ 168 Space: "http://www.w3.org/2000/09/xmldsig#", 169 Local: "KeyInfo", 170 }, 171 // Certificate: idpSignCert, 172 X509Data: samllib.X509Data{ 173 X509Certificates: []samllib.X509Certificate{ 174 {Data: idpSignCert}, 175 }, 176 }, 177 }, 178 } 179 idpSSODescriptor.KeyDescriptors = append(idpSSODescriptor.KeyDescriptors, *keyDescriptor) 180 break 181 } 182 183 b.serviceProviders[acsURL] = &sp 184 } 185 186 b.logger.Info( 187 "successfully configured SAML identity provider", 188 zap.String("tenant_id", b.config.TenantID), 189 zap.String("application_id", b.config.ApplicationID), 190 zap.String("application_name", b.config.ApplicationName), 191 zap.Any("acs_urls", b.config.AssertionConsumerServiceURLs), 192 zap.String("login_url", b.loginURL), 193 zap.String("idp_sign_cert_location", b.config.IdpSignCertLocation), 194 zap.String("idp_metadata_location", b.config.IdpMetadataLocation), 195 zap.Any("login_icon", b.config.LoginIcon), 196 ) 197 198 b.configured = true 199 200 return nil 201 } 202 203 // GetLoginIcon returns the instance of the icon associated with the provider. 204 func (b *IdentityProvider) GetLoginIcon() *icons.LoginIcon { 205 return b.config.LoginIcon 206 } 207 208 // GetLogoutURL returns the logout URL associated with the provider. 209 func (b *IdentityProvider) GetLogoutURL() string { 210 return "" 211 } 212 213 // GetDriver returns the name of the driver associated with the provider. 214 func (b *IdentityProvider) GetDriver() string { 215 return b.config.Driver 216 } 217 218 // GetIdentityTokenCookieName returns the name of the identity token cookie associated with the provider. 219 func (b *IdentityProvider) GetIdentityTokenCookieName() string { 220 return "" 221 }