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  }