auth_options.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. package gophercloud
  2. /*
  3. AuthOptions stores information needed to authenticate to an OpenStack Cloud.
  4. You can populate one manually, or use a provider's AuthOptionsFromEnv() function
  5. to read relevant information from the standard environment variables. Pass one
  6. to a provider's AuthenticatedClient function to authenticate and obtain a
  7. ProviderClient representing an active session on that provider.
  8. Its fields are the union of those recognized by each identity implementation and
  9. provider.
  10. An example of manually providing authentication information:
  11. opts := gophercloud.AuthOptions{
  12. IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
  13. Username: "{username}",
  14. Password: "{password}",
  15. TenantID: "{tenant_id}",
  16. }
  17. provider, err := openstack.AuthenticatedClient(opts)
  18. An example of using AuthOptionsFromEnv(), where the environment variables can
  19. be read from a file, such as a standard openrc file:
  20. opts, err := openstack.AuthOptionsFromEnv()
  21. provider, err := openstack.AuthenticatedClient(opts)
  22. */
  23. type AuthOptions struct {
  24. // IdentityEndpoint specifies the HTTP endpoint that is required to work with
  25. // the Identity API of the appropriate version. While it's ultimately needed by
  26. // all of the identity services, it will often be populated by a provider-level
  27. // function.
  28. //
  29. // The IdentityEndpoint is typically referred to as the "auth_url" or
  30. // "OS_AUTH_URL" in the information provided by the cloud operator.
  31. IdentityEndpoint string `json:"-"`
  32. // Username is required if using Identity V2 API. Consult with your provider's
  33. // control panel to discover your account's username. In Identity V3, either
  34. // UserID or a combination of Username and DomainID or DomainName are needed.
  35. Username string `json:"username,omitempty"`
  36. UserID string `json:"-"`
  37. Password string `json:"password,omitempty"`
  38. // At most one of DomainID and DomainName must be provided if using Username
  39. // with Identity V3. Otherwise, either are optional.
  40. DomainID string `json:"-"`
  41. DomainName string `json:"name,omitempty"`
  42. // The TenantID and TenantName fields are optional for the Identity V2 API.
  43. // The same fields are known as project_id and project_name in the Identity
  44. // V3 API, but are collected as TenantID and TenantName here in both cases.
  45. // Some providers allow you to specify a TenantName instead of the TenantId.
  46. // Some require both. Your provider's authentication policies will determine
  47. // how these fields influence authentication.
  48. // If DomainID or DomainName are provided, they will also apply to TenantName.
  49. // It is not currently possible to authenticate with Username and a Domain
  50. // and scope to a Project in a different Domain by using TenantName. To
  51. // accomplish that, the ProjectID will need to be provided as the TenantID
  52. // option.
  53. TenantID string `json:"tenantId,omitempty"`
  54. TenantName string `json:"tenantName,omitempty"`
  55. // AllowReauth should be set to true if you grant permission for Gophercloud to
  56. // cache your credentials in memory, and to allow Gophercloud to attempt to
  57. // re-authenticate automatically if/when your token expires. If you set it to
  58. // false, it will not cache these settings, but re-authentication will not be
  59. // possible. This setting defaults to false.
  60. //
  61. // NOTE: The reauth function will try to re-authenticate endlessly if left
  62. // unchecked. The way to limit the number of attempts is to provide a custom
  63. // HTTP client to the provider client and provide a transport that implements
  64. // the RoundTripper interface and stores the number of failed retries. For an
  65. // example of this, see here:
  66. // https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
  67. AllowReauth bool `json:"-"`
  68. // TokenID allows users to authenticate (possibly as another user) with an
  69. // authentication token ID.
  70. TokenID string `json:"-"`
  71. // Scope determines the scoping of the authentication request.
  72. Scope *AuthScope `json:"-"`
  73. }
  74. // AuthScope allows a created token to be limited to a specific domain or project.
  75. type AuthScope struct {
  76. ProjectID string
  77. ProjectName string
  78. DomainID string
  79. DomainName string
  80. }
  81. // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
  82. // interface in the v2 tokens package
  83. func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
  84. // Populate the request map.
  85. authMap := make(map[string]interface{})
  86. if opts.Username != "" {
  87. if opts.Password != "" {
  88. authMap["passwordCredentials"] = map[string]interface{}{
  89. "username": opts.Username,
  90. "password": opts.Password,
  91. }
  92. } else {
  93. return nil, ErrMissingInput{Argument: "Password"}
  94. }
  95. } else if opts.TokenID != "" {
  96. authMap["token"] = map[string]interface{}{
  97. "id": opts.TokenID,
  98. }
  99. } else {
  100. return nil, ErrMissingInput{Argument: "Username"}
  101. }
  102. if opts.TenantID != "" {
  103. authMap["tenantId"] = opts.TenantID
  104. }
  105. if opts.TenantName != "" {
  106. authMap["tenantName"] = opts.TenantName
  107. }
  108. return map[string]interface{}{"auth": authMap}, nil
  109. }
  110. func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
  111. type domainReq struct {
  112. ID *string `json:"id,omitempty"`
  113. Name *string `json:"name,omitempty"`
  114. }
  115. type projectReq struct {
  116. Domain *domainReq `json:"domain,omitempty"`
  117. Name *string `json:"name,omitempty"`
  118. ID *string `json:"id,omitempty"`
  119. }
  120. type userReq struct {
  121. ID *string `json:"id,omitempty"`
  122. Name *string `json:"name,omitempty"`
  123. Password string `json:"password"`
  124. Domain *domainReq `json:"domain,omitempty"`
  125. }
  126. type passwordReq struct {
  127. User userReq `json:"user"`
  128. }
  129. type tokenReq struct {
  130. ID string `json:"id"`
  131. }
  132. type identityReq struct {
  133. Methods []string `json:"methods"`
  134. Password *passwordReq `json:"password,omitempty"`
  135. Token *tokenReq `json:"token,omitempty"`
  136. }
  137. type authReq struct {
  138. Identity identityReq `json:"identity"`
  139. }
  140. type request struct {
  141. Auth authReq `json:"auth"`
  142. }
  143. // Populate the request structure based on the provided arguments. Create and return an error
  144. // if insufficient or incompatible information is present.
  145. var req request
  146. if opts.Password == "" {
  147. if opts.TokenID != "" {
  148. // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
  149. // parameters.
  150. if opts.Username != "" {
  151. return nil, ErrUsernameWithToken{}
  152. }
  153. if opts.UserID != "" {
  154. return nil, ErrUserIDWithToken{}
  155. }
  156. if opts.DomainID != "" {
  157. return nil, ErrDomainIDWithToken{}
  158. }
  159. if opts.DomainName != "" {
  160. return nil, ErrDomainNameWithToken{}
  161. }
  162. // Configure the request for Token authentication.
  163. req.Auth.Identity.Methods = []string{"token"}
  164. req.Auth.Identity.Token = &tokenReq{
  165. ID: opts.TokenID,
  166. }
  167. } else {
  168. // If no password or token ID are available, authentication can't continue.
  169. return nil, ErrMissingPassword{}
  170. }
  171. } else {
  172. // Password authentication.
  173. req.Auth.Identity.Methods = []string{"password"}
  174. // At least one of Username and UserID must be specified.
  175. if opts.Username == "" && opts.UserID == "" {
  176. return nil, ErrUsernameOrUserID{}
  177. }
  178. if opts.Username != "" {
  179. // If Username is provided, UserID may not be provided.
  180. if opts.UserID != "" {
  181. return nil, ErrUsernameOrUserID{}
  182. }
  183. // Either DomainID or DomainName must also be specified.
  184. if opts.DomainID == "" && opts.DomainName == "" {
  185. return nil, ErrDomainIDOrDomainName{}
  186. }
  187. if opts.DomainID != "" {
  188. if opts.DomainName != "" {
  189. return nil, ErrDomainIDOrDomainName{}
  190. }
  191. // Configure the request for Username and Password authentication with a DomainID.
  192. req.Auth.Identity.Password = &passwordReq{
  193. User: userReq{
  194. Name: &opts.Username,
  195. Password: opts.Password,
  196. Domain: &domainReq{ID: &opts.DomainID},
  197. },
  198. }
  199. }
  200. if opts.DomainName != "" {
  201. // Configure the request for Username and Password authentication with a DomainName.
  202. req.Auth.Identity.Password = &passwordReq{
  203. User: userReq{
  204. Name: &opts.Username,
  205. Password: opts.Password,
  206. Domain: &domainReq{Name: &opts.DomainName},
  207. },
  208. }
  209. }
  210. }
  211. if opts.UserID != "" {
  212. // If UserID is specified, neither DomainID nor DomainName may be.
  213. if opts.DomainID != "" {
  214. return nil, ErrDomainIDWithUserID{}
  215. }
  216. if opts.DomainName != "" {
  217. return nil, ErrDomainNameWithUserID{}
  218. }
  219. // Configure the request for UserID and Password authentication.
  220. req.Auth.Identity.Password = &passwordReq{
  221. User: userReq{ID: &opts.UserID, Password: opts.Password},
  222. }
  223. }
  224. }
  225. b, err := BuildRequestBody(req, "")
  226. if err != nil {
  227. return nil, err
  228. }
  229. if len(scope) != 0 {
  230. b["auth"].(map[string]interface{})["scope"] = scope
  231. }
  232. return b, nil
  233. }
  234. func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
  235. // For backwards compatibility.
  236. // If AuthOptions.Scope was not set, try to determine it.
  237. // This works well for common scenarios.
  238. if opts.Scope == nil {
  239. opts.Scope = new(AuthScope)
  240. if opts.TenantID != "" {
  241. opts.Scope.ProjectID = opts.TenantID
  242. } else {
  243. if opts.TenantName != "" {
  244. opts.Scope.ProjectName = opts.TenantName
  245. opts.Scope.DomainID = opts.DomainID
  246. opts.Scope.DomainName = opts.DomainName
  247. }
  248. }
  249. }
  250. if opts.Scope.ProjectName != "" {
  251. // ProjectName provided: either DomainID or DomainName must also be supplied.
  252. // ProjectID may not be supplied.
  253. if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
  254. return nil, ErrScopeDomainIDOrDomainName{}
  255. }
  256. if opts.Scope.ProjectID != "" {
  257. return nil, ErrScopeProjectIDOrProjectName{}
  258. }
  259. if opts.Scope.DomainID != "" {
  260. // ProjectName + DomainID
  261. return map[string]interface{}{
  262. "project": map[string]interface{}{
  263. "name": &opts.Scope.ProjectName,
  264. "domain": map[string]interface{}{"id": &opts.Scope.DomainID},
  265. },
  266. }, nil
  267. }
  268. if opts.Scope.DomainName != "" {
  269. // ProjectName + DomainName
  270. return map[string]interface{}{
  271. "project": map[string]interface{}{
  272. "name": &opts.Scope.ProjectName,
  273. "domain": map[string]interface{}{"name": &opts.Scope.DomainName},
  274. },
  275. }, nil
  276. }
  277. } else if opts.Scope.ProjectID != "" {
  278. // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
  279. if opts.Scope.DomainID != "" {
  280. return nil, ErrScopeProjectIDAlone{}
  281. }
  282. if opts.Scope.DomainName != "" {
  283. return nil, ErrScopeProjectIDAlone{}
  284. }
  285. // ProjectID
  286. return map[string]interface{}{
  287. "project": map[string]interface{}{
  288. "id": &opts.Scope.ProjectID,
  289. },
  290. }, nil
  291. } else if opts.Scope.DomainID != "" {
  292. // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
  293. if opts.Scope.DomainName != "" {
  294. return nil, ErrScopeDomainIDOrDomainName{}
  295. }
  296. // DomainID
  297. return map[string]interface{}{
  298. "domain": map[string]interface{}{
  299. "id": &opts.Scope.DomainID,
  300. },
  301. }, nil
  302. } else if opts.Scope.DomainName != "" {
  303. // DomainName
  304. return map[string]interface{}{
  305. "domain": map[string]interface{}{
  306. "name": &opts.Scope.DomainName,
  307. },
  308. }, nil
  309. }
  310. return nil, nil
  311. }
  312. func (opts AuthOptions) CanReauth() bool {
  313. return opts.AllowReauth
  314. }