github.com/Axway/agent-sdk@v1.1.101/pkg/apic/specsecuritybuilder.go (about)

     1  package apic
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/Axway/agent-sdk/pkg/util"
     7  	"github.com/getkin/kin-openapi/openapi2"
     8  	"github.com/getkin/kin-openapi/openapi3"
     9  	"golang.org/x/text/cases"
    10  	"golang.org/x/text/language"
    11  )
    12  
    13  const (
    14  	oas2              = 2
    15  	oas3              = 3
    16  	httpScheme        = "http"
    17  	cookie            = "cookie"
    18  	header            = "header"
    19  	query             = "query"
    20  	implicit          = "implicit"
    21  	authorizationCode = "authorizationCode"
    22  	clientCredentials = "clientCredentials"
    23  	password          = "password"
    24  	accessCode        = "accessCode"
    25  	application       = "application"
    26  )
    27  
    28  // used by oas spec parsers to start the builder
    29  func newSpecSecurityBuilder(oasMajorVersion int) SecurityBuilder {
    30  	return &specSecurity{
    31  		oasMajorVersion: oasMajorVersion,
    32  	}
    33  }
    34  
    35  // first select the type of security we are building
    36  type SecurityBuilder interface {
    37  	HTTPBasic() HTTPBasicSecurityBuilder
    38  	APIKey() APIKeySecurityBuilder
    39  	OAuth() OAuthSecurityBuilder
    40  	Bearer() BearerSecurityBuilder
    41  	OpenID() OpenIDSecurityBuilder
    42  }
    43  
    44  type specSecurity struct {
    45  	oasMajorVersion int
    46  }
    47  
    48  func (s *specSecurity) HTTPBasic() HTTPBasicSecurityBuilder {
    49  	return &httpBasicSecurity{
    50  		specSecurity: s,
    51  	}
    52  }
    53  
    54  type HTTPBasicSecurityBuilder interface {
    55  	Build() map[string]interface{}
    56  }
    57  
    58  type httpBasicSecurity struct {
    59  	*specSecurity
    60  }
    61  
    62  // create http basic scheme
    63  func (s *httpBasicSecurity) Build() map[string]interface{} {
    64  	const (
    65  		name  = "basicAuth"
    66  		basic = "basic"
    67  	)
    68  
    69  	if s.oasMajorVersion == 2 {
    70  		return map[string]interface{}{
    71  			name: &openapi2.SecurityScheme{
    72  				Type: basic,
    73  			},
    74  		}
    75  	}
    76  	return map[string]interface{}{
    77  		name: &openapi3.SecurityScheme{
    78  			Type:   httpScheme,
    79  			Scheme: basic,
    80  		},
    81  	}
    82  }
    83  
    84  func (s *specSecurity) APIKey() APIKeySecurityBuilder {
    85  	return &apiKeySecurity{
    86  		specSecurity: s,
    87  		locations:    []string{},
    88  	}
    89  }
    90  
    91  type APIKeySecurityBuilder interface {
    92  	Build() map[string]interface{}
    93  	InCookie() APIKeySecurityBuilder // quests are the same for cookie vs api key in query or header
    94  	InHeader() APIKeySecurityBuilder
    95  	InQueryParam() APIKeySecurityBuilder
    96  	SetArgumentName(argName string) APIKeySecurityBuilder
    97  }
    98  
    99  type apiKeySecurity struct {
   100  	*specSecurity
   101  	locations []string
   102  	argName   string
   103  }
   104  
   105  func (s *apiKeySecurity) InCookie() APIKeySecurityBuilder {
   106  	s.locations = append(s.locations, cookie)
   107  	return s
   108  }
   109  
   110  func (s *apiKeySecurity) InHeader() APIKeySecurityBuilder {
   111  	s.locations = append(s.locations, header)
   112  	return s
   113  }
   114  
   115  func (s *apiKeySecurity) InQueryParam() APIKeySecurityBuilder {
   116  	s.locations = append(s.locations, query)
   117  	return s
   118  }
   119  
   120  func (s *apiKeySecurity) SetArgumentName(argName string) APIKeySecurityBuilder {
   121  	s.argName = argName
   122  	return s
   123  }
   124  
   125  // create api key security type
   126  func (s *apiKeySecurity) Build() map[string]interface{} {
   127  	const apiKey = "apiKey"
   128  
   129  	output := map[string]interface{}{}
   130  
   131  	for _, location := range s.locations {
   132  		name := fmt.Sprintf("%v%v", apiKey, cases.Title(language.English).String(location))
   133  
   134  		if s.oasMajorVersion == 2 {
   135  			if location == cookie {
   136  				// only supported on oas3, return empty for oas2
   137  				continue
   138  			}
   139  
   140  			output[name] = &openapi2.SecurityScheme{
   141  				Name: s.argName,
   142  				In:   location,
   143  				Type: apiKey,
   144  			}
   145  			continue
   146  		}
   147  
   148  		output[name] = &openapi3.SecurityScheme{
   149  			Name: s.argName,
   150  			In:   location,
   151  			Type: apiKey,
   152  		}
   153  	}
   154  
   155  	return output
   156  }
   157  
   158  type oAuthFlow struct {
   159  	flow       string
   160  	authURL    string
   161  	tokenURL   string
   162  	refreshURL string
   163  	scopes     map[string]string
   164  }
   165  
   166  func NewOAuthFlowBuilder() OAuthFlowBuilder {
   167  	return &oAuthFlow{}
   168  }
   169  
   170  // oauth flow options, setting flow type should be last, not all other methods are required
   171  type OAuthFlowBuilder interface {
   172  	SetScopes(map[string]string) OAuthFlowBuilder
   173  	AddScope(scope, description string) OAuthFlowBuilder
   174  	SetAuthorizationURL(url string) OAuthFlowBuilder
   175  	SetRefreshURL(url string) OAuthFlowBuilder
   176  	SetTokenURL(url string) OAuthFlowBuilder
   177  	Implicit() *oAuthFlow
   178  	Password() *oAuthFlow
   179  	AuthorizationCode() *oAuthFlow
   180  	ClientCredentials() *oAuthFlow
   181  }
   182  
   183  func (s *oAuthFlow) SetScopes(scopes map[string]string) OAuthFlowBuilder {
   184  	s.scopes = scopes
   185  	return s
   186  }
   187  
   188  func (s *oAuthFlow) AddScope(scope, description string) OAuthFlowBuilder {
   189  	s.scopes[scope] = description
   190  	return s
   191  }
   192  
   193  func (s *oAuthFlow) SetTokenURL(url string) OAuthFlowBuilder {
   194  	s.tokenURL = url
   195  	return s
   196  }
   197  
   198  func (s *oAuthFlow) SetAuthorizationURL(url string) OAuthFlowBuilder {
   199  	s.authURL = url
   200  	return s
   201  }
   202  
   203  func (s *oAuthFlow) SetRefreshURL(url string) OAuthFlowBuilder {
   204  	s.refreshURL = url
   205  	return s
   206  }
   207  
   208  func (s *oAuthFlow) Implicit() *oAuthFlow {
   209  	s.flow = implicit
   210  	return s
   211  }
   212  
   213  func (s *oAuthFlow) Password() *oAuthFlow {
   214  	s.flow = password
   215  	return s
   216  }
   217  
   218  func (s *oAuthFlow) AuthorizationCode() *oAuthFlow {
   219  	s.flow = authorizationCode
   220  	return s
   221  }
   222  
   223  func (s *oAuthFlow) ClientCredentials() *oAuthFlow {
   224  	s.flow = clientCredentials
   225  	return s
   226  }
   227  
   228  type OAuthSecurityBuilder interface {
   229  	AddFlow(flow *oAuthFlow) OAuthSecurityBuilder
   230  	Build() map[string]interface{}
   231  }
   232  
   233  type oAuthSecurity struct {
   234  	*specSecurity
   235  	flows []*oAuthFlow
   236  }
   237  
   238  func (s *specSecurity) OAuth() OAuthSecurityBuilder {
   239  	return &oAuthSecurity{
   240  		specSecurity: s,
   241  		flows:        []*oAuthFlow{},
   242  	}
   243  }
   244  
   245  func (s *oAuthSecurity) AddFlow(flow *oAuthFlow) OAuthSecurityBuilder {
   246  	s.flows = append(s.flows, flow)
   247  	return s
   248  }
   249  
   250  func (s *oAuthSecurity) Build() map[string]interface{} {
   251  	const oauth2 = "oauth2"
   252  
   253  	if s.oasMajorVersion == 2 {
   254  		// create separate scheme for each flow type
   255  		oauthFlows := map[string]interface{}{}
   256  		for _, f := range s.flows {
   257  			// adjust the name of the flow for oas2 support
   258  			if f.flow == authorizationCode {
   259  				f.flow = accessCode
   260  			} else if f.flow == clientCredentials {
   261  				f.flow = application
   262  			}
   263  
   264  			fName := fmt.Sprintf("%v%v", oauth2, cases.Title(language.English).String(f.flow))
   265  			oauthFlows[fName] = &openapi2.SecurityScheme{
   266  				Type:             oauth2,
   267  				Flow:             f.flow,
   268  				Scopes:           util.OrderStringsInMap(f.scopes),
   269  				AuthorizationURL: f.authURL,
   270  				TokenURL:         f.tokenURL,
   271  			}
   272  		}
   273  		return util.OrderStringsInMap(oauthFlows)
   274  	}
   275  
   276  	// create single scheme with all flows
   277  	oauthFlows := &openapi3.OAuthFlows{}
   278  	for _, f := range s.flows {
   279  		oFlow := &openapi3.OAuthFlow{
   280  			AuthorizationURL: f.authURL,
   281  			RefreshURL:       f.refreshURL,
   282  			TokenURL:         f.tokenURL,
   283  			Scopes:           util.OrderStringsInMap(f.scopes),
   284  		}
   285  		switch f.flow {
   286  		case authorizationCode:
   287  			oauthFlows.AuthorizationCode = oFlow
   288  		case password:
   289  			oauthFlows.Password = oFlow
   290  		case clientCredentials:
   291  			oauthFlows.ClientCredentials = oFlow
   292  		case implicit:
   293  			oauthFlows.Implicit = oFlow
   294  		}
   295  	}
   296  	return map[string]interface{}{
   297  		oauth2: &openapi3.SecurityScheme{
   298  			Type:  oauth2,
   299  			Flows: oauthFlows,
   300  		},
   301  	}
   302  }
   303  
   304  func (s *specSecurity) Bearer() BearerSecurityBuilder {
   305  	return &bearerSecurity{
   306  		specSecurity: s,
   307  	}
   308  }
   309  
   310  type BearerSecurityBuilder interface {
   311  	Build() map[string]interface{}
   312  	SetFormat(format string) BearerSecurityBuilder
   313  }
   314  
   315  type bearerSecurity struct {
   316  	*specSecurity
   317  	format string
   318  }
   319  
   320  func (s *bearerSecurity) SetFormat(format string) BearerSecurityBuilder {
   321  	s.format = format
   322  	return s
   323  }
   324  
   325  func (s *bearerSecurity) Build() map[string]interface{} {
   326  	const (
   327  		name   = "bearerAuth"
   328  		bearer = "bearer"
   329  	)
   330  
   331  	if s.oasMajorVersion == 2 {
   332  		// only supported on oas3, return empty for oas2
   333  		return map[string]interface{}{}
   334  	}
   335  	return map[string]interface{}{
   336  		name: &openapi3.SecurityScheme{
   337  			Type:         httpScheme,
   338  			Scheme:       bearer,
   339  			BearerFormat: s.format,
   340  		},
   341  	}
   342  }
   343  
   344  func (s *specSecurity) OpenID() OpenIDSecurityBuilder {
   345  	return &openIDSecurity{
   346  		specSecurity: s,
   347  	}
   348  }
   349  
   350  type OpenIDSecurityBuilder interface {
   351  	Build() map[string]interface{}
   352  	SetURL(url string) OpenIDSecurityBuilder
   353  }
   354  
   355  type openIDSecurity struct {
   356  	*specSecurity
   357  	url string
   358  }
   359  
   360  func (s *openIDSecurity) SetURL(url string) OpenIDSecurityBuilder {
   361  	s.url = url
   362  	return s
   363  }
   364  
   365  func (s *openIDSecurity) Build() map[string]interface{} {
   366  	const (
   367  		name          = "openId"
   368  		openIdConnect = "openIdConnect"
   369  	)
   370  
   371  	if s.oasMajorVersion == 2 {
   372  		// only supported on oas3, return empty for oas2
   373  		return map[string]interface{}{}
   374  	}
   375  	return map[string]interface{}{
   376  		name: &openapi3.SecurityScheme{
   377  			Type:             openIdConnect,
   378  			OpenIdConnectUrl: s.url,
   379  		},
   380  	}
   381  }