github.com/ccrossley/goa@v1.3.1/design/apidsl/security.go (about)

     1  package apidsl
     2  
     3  import (
     4  	"github.com/goadesign/goa/design"
     5  	"github.com/goadesign/goa/dslengine"
     6  )
     7  
     8  // Security defines an authentication requirements to access a goa Action.  When defined on a
     9  // Resource, it applies to all Actions, unless overriden by individual actions.  When defined at the
    10  // API level, it will apply to all resources by default, following the same logic.
    11  //
    12  // The scheme refers to previous definitions of either OAuth2Security, BasicAuthSecurity,
    13  // APIKeySecurity or JWTSecurity.  It can be a string, corresponding to the first parameter of
    14  // those definitions, or a SecuritySchemeDefinition, returned by those same functions. Examples:
    15  //
    16  //    Security(BasicAuth)
    17  //
    18  //    Security("oauth2", func() {
    19  //        Scope("api:read")  // Requires "api:read" oauth2 scope
    20  //    })
    21  //
    22  func Security(scheme interface{}, dsl ...func()) {
    23  	var def *design.SecurityDefinition
    24  	switch val := scheme.(type) {
    25  	case string:
    26  		def = &design.SecurityDefinition{}
    27  		for _, scheme := range design.Design.SecuritySchemes {
    28  			if scheme.SchemeName == val {
    29  				def.Scheme = scheme
    30  			}
    31  		}
    32  		if def.Scheme == nil {
    33  			dslengine.ReportError("security scheme %q not found", val)
    34  			return
    35  		}
    36  	case *design.SecuritySchemeDefinition:
    37  		def = &design.SecurityDefinition{Scheme: val}
    38  	default:
    39  		dslengine.ReportError("invalid value for 'scheme' parameter, specify a string or a *SecuritySchemeDefinition")
    40  		return
    41  	}
    42  
    43  	if len(dsl) != 0 {
    44  		if !dslengine.Execute(dsl[0], def) {
    45  			return
    46  		}
    47  	}
    48  
    49  	parentDef := dslengine.CurrentDefinition()
    50  	switch parent := parentDef.(type) {
    51  	case *design.ActionDefinition:
    52  		parent.Security = def
    53  	case *design.FileServerDefinition:
    54  		parent.Security = def
    55  	case *design.ResourceDefinition:
    56  		parent.Security = def
    57  	case *design.APIDefinition:
    58  		parent.Security = def
    59  	default:
    60  		dslengine.IncompatibleDSL()
    61  		return
    62  	}
    63  }
    64  
    65  // NoSecurity resets the authentication schemes for an Action or a Resource. It also prevents
    66  // fallback to Resource or API-defined Security.
    67  func NoSecurity() {
    68  	def := &design.SecurityDefinition{
    69  		Scheme: &design.SecuritySchemeDefinition{Kind: design.NoSecurityKind},
    70  	}
    71  
    72  	parentDef := dslengine.CurrentDefinition()
    73  	switch parent := parentDef.(type) {
    74  	case *design.ActionDefinition:
    75  		parent.Security = def
    76  	case *design.FileServerDefinition:
    77  		parent.Security = def
    78  	case *design.ResourceDefinition:
    79  		parent.Security = def
    80  	default:
    81  		dslengine.IncompatibleDSL()
    82  		return
    83  	}
    84  }
    85  
    86  // BasicAuthSecurity defines a "basic" security scheme for the API.
    87  //
    88  // Example:
    89  //
    90  //     BasicAuthSecurity("password", func() {
    91  //         Description("Use your own password!")
    92  //     })
    93  //
    94  func BasicAuthSecurity(name string, dsl ...func()) *design.SecuritySchemeDefinition {
    95  	switch dslengine.CurrentDefinition().(type) {
    96  	case *design.APIDefinition, *dslengine.TopLevelDefinition:
    97  	default:
    98  		dslengine.IncompatibleDSL()
    99  		return nil
   100  	}
   101  
   102  	if securitySchemeRedefined(name) {
   103  		return nil
   104  	}
   105  
   106  	def := &design.SecuritySchemeDefinition{
   107  		Kind:       design.BasicAuthSecurityKind,
   108  		SchemeName: name,
   109  		Type:       "basic",
   110  	}
   111  
   112  	if len(dsl) != 0 {
   113  		def.DSLFunc = dsl[0]
   114  	}
   115  
   116  	design.Design.SecuritySchemes = append(design.Design.SecuritySchemes, def)
   117  
   118  	return def
   119  }
   120  
   121  func securitySchemeRedefined(name string) bool {
   122  	for _, previousScheme := range design.Design.SecuritySchemes {
   123  		if previousScheme.SchemeName == name {
   124  			dslengine.ReportError("cannot redefine SecurityScheme with name %q", name)
   125  			return true
   126  		}
   127  	}
   128  	return false
   129  }
   130  
   131  // APIKeySecurity defines an "apiKey" security scheme available throughout the API.
   132  //
   133  // Example:
   134  //
   135  //     APIKeySecurity("key", func() {
   136  //          Description("Shared secret")
   137  //          Header("Authorization")
   138  //    })
   139  //
   140  func APIKeySecurity(name string, dsl ...func()) *design.SecuritySchemeDefinition {
   141  	switch dslengine.CurrentDefinition().(type) {
   142  	case *design.APIDefinition, *dslengine.TopLevelDefinition:
   143  	default:
   144  		dslengine.IncompatibleDSL()
   145  		return nil
   146  	}
   147  
   148  	if securitySchemeRedefined(name) {
   149  		return nil
   150  	}
   151  
   152  	def := &design.SecuritySchemeDefinition{
   153  		Kind:       design.APIKeySecurityKind,
   154  		SchemeName: name,
   155  		Type:       "apiKey",
   156  	}
   157  
   158  	if len(dsl) != 0 {
   159  		def.DSLFunc = dsl[0]
   160  	}
   161  
   162  	design.Design.SecuritySchemes = append(design.Design.SecuritySchemes, def)
   163  
   164  	return def
   165  }
   166  
   167  // OAuth2Security defines an OAuth2 security scheme. The child DSL must define one and exactly one
   168  // flow. One of AccessCodeFlow, ImplicitFlow, PasswordFlow or ApplicationFlow. Each flow defines
   169  // endpoints for retrieving OAuth2 authorization codes and/or refresh and access tokens. The
   170  // endpoint URLs may be complete or may be just a path in which case the API scheme and host are
   171  // used to build the full URL. See for example [Aaron Parecki's
   172  // writeup](https://aaronparecki.com/2012/07/29/2/oauth2-simplified) for additional details on
   173  // OAuth2 flows.
   174  //
   175  // The OAuth2 DSL also allows for defining scopes that must be associated with the incoming request
   176  // token for successful authorization.
   177  //
   178  // Example:
   179  //
   180  //    OAuth2Security("googAuth", func() {
   181  //        AccessCodeFlow("/authorization", "/token")
   182  //     // ImplicitFlow("/authorization")
   183  //     // PasswordFlow("/token"...)
   184  //     // ApplicationFlow("/token")
   185  //
   186  //        Scope("my_system:write", "Write to the system")
   187  //        Scope("my_system:read", "Read anything in there")
   188  //    })
   189  //
   190  func OAuth2Security(name string, dsl ...func()) *design.SecuritySchemeDefinition {
   191  	switch dslengine.CurrentDefinition().(type) {
   192  	case *design.APIDefinition, *dslengine.TopLevelDefinition:
   193  	default:
   194  		dslengine.IncompatibleDSL()
   195  		return nil
   196  	}
   197  
   198  	if securitySchemeRedefined(name) {
   199  		return nil
   200  	}
   201  
   202  	def := &design.SecuritySchemeDefinition{
   203  		SchemeName: name,
   204  		Kind:       design.OAuth2SecurityKind,
   205  		Type:       "oauth2",
   206  	}
   207  
   208  	if len(dsl) != 0 {
   209  		def.DSLFunc = dsl[0]
   210  	}
   211  
   212  	design.Design.SecuritySchemes = append(design.Design.SecuritySchemes, def)
   213  
   214  	return def
   215  }
   216  
   217  // JWTSecurity defines an APIKey security scheme, with support for Scopes and a TokenURL.
   218  //
   219  // Since Scopes and TokenURLs are not compatible with the Swagger specification, the swagger
   220  // generator inserts comments in the description of the different elements on which they are
   221  // defined.
   222  //
   223  // Example:
   224  //
   225  //    JWTSecurity("jwt", func() {
   226  //        Header("Authorization")
   227  //        TokenURL("https://example.com/token")
   228  //        Scope("my_system:write", "Write to the system")
   229  //        Scope("my_system:read", "Read anything in there")
   230  //    })
   231  //
   232  func JWTSecurity(name string, dsl ...func()) *design.SecuritySchemeDefinition {
   233  	switch dslengine.CurrentDefinition().(type) {
   234  	case *design.APIDefinition, *dslengine.TopLevelDefinition:
   235  	default:
   236  		dslengine.IncompatibleDSL()
   237  		return nil
   238  	}
   239  
   240  	if securitySchemeRedefined(name) {
   241  		return nil
   242  	}
   243  
   244  	def := &design.SecuritySchemeDefinition{
   245  		SchemeName: name,
   246  		Kind:       design.JWTSecurityKind,
   247  		Type:       "apiKey",
   248  	}
   249  
   250  	if len(dsl) != 0 {
   251  		def.DSLFunc = dsl[0]
   252  	}
   253  
   254  	design.Design.SecuritySchemes = append(design.Design.SecuritySchemes, def)
   255  
   256  	return def
   257  }
   258  
   259  // Scope defines an authorization scope. Used within SecurityScheme, a description may be provided
   260  // explaining what the scope means. Within a Security block, only a scope is needed.
   261  func Scope(name string, desc ...string) {
   262  	switch current := dslengine.CurrentDefinition().(type) {
   263  	case *design.SecurityDefinition:
   264  		if len(desc) >= 1 {
   265  			dslengine.ReportError("too many arguments")
   266  			return
   267  		}
   268  		current.Scopes = append(current.Scopes, name)
   269  	case *design.SecuritySchemeDefinition:
   270  		if len(desc) > 1 {
   271  			dslengine.ReportError("too many arguments")
   272  			return
   273  		}
   274  		if current.Scopes == nil {
   275  			current.Scopes = make(map[string]string)
   276  		}
   277  		d := "no description"
   278  		if len(desc) == 1 {
   279  			d = desc[0]
   280  		}
   281  		current.Scopes[name] = d
   282  	default:
   283  		dslengine.IncompatibleDSL()
   284  	}
   285  }
   286  
   287  // inHeader is called by `Header()`, see documentation there.
   288  func inHeader(headerName string) {
   289  	if current, ok := dslengine.CurrentDefinition().(*design.SecuritySchemeDefinition); ok {
   290  		if current.Kind == design.APIKeySecurityKind || current.Kind == design.JWTSecurityKind {
   291  			if current.In != "" {
   292  				dslengine.ReportError("'In' previously defined through Header or Query")
   293  				return
   294  			}
   295  			current.In = "header"
   296  			current.Name = headerName
   297  			return
   298  		}
   299  	}
   300  	dslengine.IncompatibleDSL()
   301  }
   302  
   303  // Query defines that an APIKeySecurity or JWTSecurity implementation must check in the query
   304  // parameter named "parameterName" to get the api key.
   305  func Query(parameterName string) {
   306  	if current, ok := dslengine.CurrentDefinition().(*design.SecuritySchemeDefinition); ok {
   307  		if current.Kind == design.APIKeySecurityKind || current.Kind == design.JWTSecurityKind {
   308  			if current.In != "" {
   309  				dslengine.ReportError("'In' previously defined through Header or Query")
   310  				return
   311  			}
   312  			current.In = "query"
   313  			current.Name = parameterName
   314  			return
   315  		}
   316  	}
   317  	dslengine.IncompatibleDSL()
   318  }
   319  
   320  // AccessCodeFlow defines an "access code" OAuth2 flow.  Use within an OAuth2Security definition.
   321  func AccessCodeFlow(authorizationURL, tokenURL string) {
   322  	if current, ok := dslengine.CurrentDefinition().(*design.SecuritySchemeDefinition); ok {
   323  		if current.Kind == design.OAuth2SecurityKind {
   324  			current.Flow = "accessCode"
   325  			current.AuthorizationURL = authorizationURL
   326  			current.TokenURL = tokenURL
   327  			return
   328  		}
   329  	}
   330  	dslengine.IncompatibleDSL()
   331  }
   332  
   333  // ApplicationFlow defines an "application" OAuth2 flow.  Use within an OAuth2Security definition.
   334  func ApplicationFlow(tokenURL string) {
   335  	if parent, ok := dslengine.CurrentDefinition().(*design.SecuritySchemeDefinition); ok {
   336  		if parent.Kind == design.OAuth2SecurityKind {
   337  			parent.Flow = "application"
   338  			parent.TokenURL = tokenURL
   339  			return
   340  		}
   341  	}
   342  	dslengine.IncompatibleDSL()
   343  }
   344  
   345  // PasswordFlow defines a "password" OAuth2 flow.  Use within an OAuth2Security definition.
   346  func PasswordFlow(tokenURL string) {
   347  	if parent, ok := dslengine.CurrentDefinition().(*design.SecuritySchemeDefinition); ok {
   348  		if parent.Kind == design.OAuth2SecurityKind {
   349  			parent.Flow = "password"
   350  			parent.TokenURL = tokenURL
   351  			return
   352  		}
   353  	}
   354  	dslengine.IncompatibleDSL()
   355  }
   356  
   357  // ImplicitFlow defines an "implicit" OAuth2 flow.  Use within an OAuth2Security definition.
   358  func ImplicitFlow(authorizationURL string) {
   359  	if parent, ok := dslengine.CurrentDefinition().(*design.SecuritySchemeDefinition); ok {
   360  		if parent.Kind == design.OAuth2SecurityKind {
   361  			parent.Flow = "implicit"
   362  			parent.AuthorizationURL = authorizationURL
   363  			return
   364  		}
   365  	}
   366  	dslengine.IncompatibleDSL()
   367  }
   368  
   369  // TokenURL defines a URL to get an access token.  If you are defining OAuth2 flows, use
   370  // `ImplicitFlow`, `PasswordFlow`, `AccessCodeFlow` or `ApplicationFlow` instead. This will set an
   371  // endpoint where you can obtain a JWT with the JWTSecurity scheme. The URL may be a complete URL
   372  // or just a path in which case the API scheme and host are used to build the full URL.
   373  func TokenURL(tokenURL string) {
   374  	if parent, ok := dslengine.CurrentDefinition().(*design.SecuritySchemeDefinition); ok {
   375  		if parent.Kind == design.JWTSecurityKind {
   376  			parent.TokenURL = tokenURL
   377  			return
   378  		}
   379  	}
   380  	dslengine.IncompatibleDSL()
   381  }