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 }