| Samuel A. Falvo II | 1d3fa66 | 2013-06-25 15:29:32 -0700 | [diff] [blame] | 1 | package gophercloud | 
 | 2 |  | 
| Samuel A. Falvo II | 5d0d74c | 2013-06-25 17:23:18 -0700 | [diff] [blame] | 3 | import ( | 
 | 4 | 	"github.com/racker/perigee" | 
 | 5 | ) | 
 | 6 |  | 
| Samuel A. Falvo II | 4e89518 | 2013-06-26 15:44:18 -0700 | [diff] [blame] | 7 | // AuthOptions lets anyone calling Authenticate() supply the required access credentials. | 
 | 8 | // At present, only Identity V2 API support exists; therefore, only Username, Password, | 
 | 9 | // and optionally, TenantId are provided.  If future Identity API versions become available, | 
 | 10 | // alternative fields unique to those versions may appear here. | 
| Samuel A. Falvo II | 1d3fa66 | 2013-06-25 15:29:32 -0700 | [diff] [blame] | 11 | type AuthOptions struct { | 
| Samuel A. Falvo II | 4e89518 | 2013-06-26 15:44:18 -0700 | [diff] [blame] | 12 | 	// Username and Password are required if using Identity V2 API. | 
 | 13 | 	// Consult with your provider's control panel to discover your | 
 | 14 | 	// account's username and password. | 
 | 15 | 	Username, Password string | 
 | 16 |  | 
| Rafael Garcia | ed5dd7f | 2013-12-06 17:00:32 -0300 | [diff] [blame^] | 17 | 	// ApiKey used for providers that support Api Key authentication | 
 | 18 | 	ApiKey string | 
 | 19 |  | 
| Samuel A. Falvo II | 4e89518 | 2013-06-26 15:44:18 -0700 | [diff] [blame] | 20 | 	// The TenantId field is optional for the Identity V2 API. | 
 | 21 | 	TenantId string | 
| Samuel A. Falvo II | 9e64f6b | 2013-07-16 14:26:50 -0700 | [diff] [blame] | 22 |  | 
| Justin Santa Barbara | 21682a4 | 2013-08-31 17:56:13 -0700 | [diff] [blame] | 23 | 	// The TenantName can be specified instead of the TenantId | 
 | 24 | 	TenantName string | 
 | 25 |  | 
| Samuel A. Falvo II | 9e64f6b | 2013-07-16 14:26:50 -0700 | [diff] [blame] | 26 | 	// AllowReauth should be set to true if you grant permission for Gophercloud to cache | 
 | 27 | 	// your credentials in memory, and to allow Gophercloud to attempt to re-authenticate | 
 | 28 | 	// automatically if/when your token expires.  If you set it to false, it will not cache | 
 | 29 | 	// these settings, but re-authentication will not be possible.  This setting defaults | 
 | 30 | 	// to false. | 
 | 31 | 	AllowReauth bool | 
| Samuel A. Falvo II | 1d3fa66 | 2013-06-25 15:29:32 -0700 | [diff] [blame] | 32 | } | 
 | 33 |  | 
| Samuel A. Falvo II | 4e89518 | 2013-06-26 15:44:18 -0700 | [diff] [blame] | 34 | // AuthContainer provides a JSON encoding wrapper for passing credentials to the Identity | 
 | 35 | // service.  You will not work with this structure directly. | 
| Samuel A. Falvo II | 5d0d74c | 2013-06-25 17:23:18 -0700 | [diff] [blame] | 36 | type AuthContainer struct { | 
 | 37 | 	Auth Auth `json:"auth"` | 
 | 38 | } | 
 | 39 |  | 
| Samuel A. Falvo II | 4e89518 | 2013-06-26 15:44:18 -0700 | [diff] [blame] | 40 | // Auth provides a JSON encoding wrapper for passing credentials to the Identity | 
 | 41 | // service.  You will not work with this structure directly. | 
| Samuel A. Falvo II | 5d0d74c | 2013-06-25 17:23:18 -0700 | [diff] [blame] | 42 | type Auth struct { | 
| Rafael Garcia | ed5dd7f | 2013-12-06 17:00:32 -0300 | [diff] [blame^] | 43 | 	PasswordCredentials *PasswordCredentials `json:"passwordCredentials,omitempty"` | 
 | 44 | 	ApiKeyCredentials   *ApiKeyCredentials   `json:"RAX-KSKEY:apiKeyCredentials,omitempty"` | 
| Samuel A. Falvo II | 839428e | 2013-06-25 18:02:24 -0700 | [diff] [blame] | 45 | 	TenantId            string              `json:"tenantId,omitempty"` | 
| Justin Santa Barbara | 21682a4 | 2013-08-31 17:56:13 -0700 | [diff] [blame] | 46 | 	TenantName          string              `json:"tenantName,omitempty"` | 
| Samuel A. Falvo II | 5d0d74c | 2013-06-25 17:23:18 -0700 | [diff] [blame] | 47 | } | 
 | 48 |  | 
| Samuel A. Falvo II | 4e89518 | 2013-06-26 15:44:18 -0700 | [diff] [blame] | 49 | // PasswordCredentials provides a JSON encoding wrapper for passing credentials to the Identity | 
 | 50 | // service.  You will not work with this structure directly. | 
| Samuel A. Falvo II | 5d0d74c | 2013-06-25 17:23:18 -0700 | [diff] [blame] | 51 | type PasswordCredentials struct { | 
 | 52 | 	Username string `json:"username"` | 
 | 53 | 	Password string `json:"password"` | 
 | 54 | } | 
 | 55 |  | 
| Rafael Garcia | ed5dd7f | 2013-12-06 17:00:32 -0300 | [diff] [blame^] | 56 |  | 
 | 57 | type ApiKeyCredentials struct { | 
 | 58 | 	Username string `json:"username"` | 
 | 59 | 	ApiKey string `json:"apiKey"` | 
 | 60 | } | 
 | 61 |  | 
| Samuel A. Falvo II | d1ee798 | 2013-06-26 14:32:45 -0700 | [diff] [blame] | 62 | // Access encapsulates the API token and its relevant fields, as well as the | 
| Samuel A. Falvo II | 2e2b877 | 2013-07-04 15:40:15 -0700 | [diff] [blame] | 63 | // services catalog that Identity API returns once authenticated. | 
| Samuel A. Falvo II | d1ee798 | 2013-06-26 14:32:45 -0700 | [diff] [blame] | 64 | type Access struct { | 
 | 65 | 	Token          Token | 
 | 66 | 	ServiceCatalog []CatalogEntry | 
 | 67 | 	User           User | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 68 | 	provider       Provider    `json:"-"` | 
| Samuel A. Falvo II | 9e64f6b | 2013-07-16 14:26:50 -0700 | [diff] [blame] | 69 | 	options        AuthOptions `json:"-"` | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 70 | 	context        *Context    `json:"-"` | 
| Samuel A. Falvo II | 5d0d74c | 2013-06-25 17:23:18 -0700 | [diff] [blame] | 71 | } | 
 | 72 |  | 
| Samuel A. Falvo II | d1ee798 | 2013-06-26 14:32:45 -0700 | [diff] [blame] | 73 | // Token encapsulates an authentication token and when it expires.  It also includes | 
 | 74 | // tenant information if available. | 
 | 75 | type Token struct { | 
 | 76 | 	Id, Expires string | 
 | 77 | 	Tenant      Tenant | 
 | 78 | } | 
 | 79 |  | 
 | 80 | // Tenant encapsulates tenant authentication information.  If, after authentication, | 
 | 81 | // no tenant information is supplied, both Id and Name will be "". | 
 | 82 | type Tenant struct { | 
 | 83 | 	Id, Name string | 
 | 84 | } | 
 | 85 |  | 
 | 86 | // User encapsulates the user credentials, and provides visibility in what | 
 | 87 | // the user can do through its role assignments. | 
 | 88 | type User struct { | 
 | 89 | 	Id, Name          string | 
 | 90 | 	XRaxDefaultRegion string `json:"RAX-AUTH:defaultRegion"` | 
 | 91 | 	Roles             []Role | 
 | 92 | } | 
 | 93 |  | 
 | 94 | // Role encapsulates a permission that a user can rely on. | 
 | 95 | type Role struct { | 
 | 96 | 	Description, Id, Name string | 
 | 97 | } | 
 | 98 |  | 
 | 99 | // CatalogEntry encapsulates a service catalog record. | 
 | 100 | type CatalogEntry struct { | 
 | 101 | 	Name, Type string | 
 | 102 | 	Endpoints  []EntryEndpoint | 
 | 103 | } | 
 | 104 |  | 
 | 105 | // EntryEndpoint encapsulates how to get to the API of some service. | 
 | 106 | type EntryEndpoint struct { | 
 | 107 | 	Region, TenantId                    string | 
 | 108 | 	PublicURL, InternalURL              string | 
 | 109 | 	VersionId, VersionInfo, VersionList string | 
 | 110 | } | 
 | 111 |  | 
| Rafael Garcia | ed5dd7f | 2013-12-06 17:00:32 -0300 | [diff] [blame^] | 112 |  | 
 | 113 | // | 
 | 114 | func getAuthCredentials(options AuthOptions) Auth { | 
 | 115 | 	if (options.ApiKey == "") { | 
 | 116 | 		return Auth{ | 
 | 117 | 			PasswordCredentials: &PasswordCredentials{ | 
 | 118 | 				Username: options.Username, | 
 | 119 | 				Password: options.Password, | 
 | 120 | 			}, | 
 | 121 | 			TenantId:   options.TenantId, | 
 | 122 | 			TenantName: options.TenantName, | 
 | 123 | 		}; | 
 | 124 | 	} else { | 
 | 125 | 		return Auth{ | 
 | 126 | 			ApiKeyCredentials: &ApiKeyCredentials{ | 
 | 127 | 				Username: options.Username, | 
 | 128 | 				ApiKey: options.ApiKey, | 
 | 129 | 			}, | 
 | 130 | 			TenantId:   options.TenantId, | 
 | 131 | 			TenantName: options.TenantName, | 
 | 132 | 		}; | 
 | 133 | 	} | 
 | 134 | } | 
 | 135 |  | 
| Samuel A. Falvo II | 9e64f6b | 2013-07-16 14:26:50 -0700 | [diff] [blame] | 136 | // papersPlease contains the common logic between authentication and re-authentication. | 
 | 137 | // The name, obviously a joke on the process of authentication, was chosen because | 
 | 138 | // of how many other entities exist in the program containing the word Auth or Authorization. | 
 | 139 | // I didn't need another one. | 
 | 140 | func (c *Context) papersPlease(p Provider, options AuthOptions) (*Access, error) { | 
| Samuel A. Falvo II | d1ee798 | 2013-06-26 14:32:45 -0700 | [diff] [blame] | 141 | 	var access *Access | 
 | 142 |  | 
| Rafael Garcia | ed5dd7f | 2013-12-06 17:00:32 -0300 | [diff] [blame^] | 143 | 	if (options.Username == "") || (options.Password == "" && options.ApiKey == "") { | 
| Samuel A. Falvo II | fd78c30 | 2013-06-25 16:35:32 -0700 | [diff] [blame] | 144 | 		return nil, ErrCredentials | 
 | 145 | 	} | 
| Samuel A. Falvo II | 5d0d74c | 2013-06-25 17:23:18 -0700 | [diff] [blame] | 146 |  | 
| Samuel A. Falvo II | 9e64f6b | 2013-07-16 14:26:50 -0700 | [diff] [blame] | 147 | 	err := perigee.Post(p.AuthEndpoint, perigee.Options{ | 
| Samuel A. Falvo II | 5d0d74c | 2013-06-25 17:23:18 -0700 | [diff] [blame] | 148 | 		CustomClient: c.httpClient, | 
| Samuel A. Falvo II | 839428e | 2013-06-25 18:02:24 -0700 | [diff] [blame] | 149 | 		ReqBody: &AuthContainer{ | 
| Rafael Garcia | ed5dd7f | 2013-12-06 17:00:32 -0300 | [diff] [blame^] | 150 | 			Auth: getAuthCredentials(options), | 
| Samuel A. Falvo II | 5d0d74c | 2013-06-25 17:23:18 -0700 | [diff] [blame] | 151 | 		}, | 
| Samuel A. Falvo II | 4e89518 | 2013-06-26 15:44:18 -0700 | [diff] [blame] | 152 | 		Results: &struct { | 
| Samuel A. Falvo II | d1ee798 | 2013-06-26 14:32:45 -0700 | [diff] [blame] | 153 | 			Access **Access `json:"access"` | 
 | 154 | 		}{ | 
 | 155 | 			&access, | 
 | 156 | 		}, | 
| Samuel A. Falvo II | 5d0d74c | 2013-06-25 17:23:18 -0700 | [diff] [blame] | 157 | 	}) | 
| Samuel A. Falvo II | 0167aaa | 2013-07-16 12:36:25 -0700 | [diff] [blame] | 158 | 	if err == nil { | 
| Samuel A. Falvo II | 9e64f6b | 2013-07-16 14:26:50 -0700 | [diff] [blame] | 159 | 		access.options = options | 
| Samuel A. Falvo II | 0167aaa | 2013-07-16 12:36:25 -0700 | [diff] [blame] | 160 | 		access.provider = p | 
| Samuel A. Falvo II | 9e64f6b | 2013-07-16 14:26:50 -0700 | [diff] [blame] | 161 | 		access.context = c | 
| Samuel A. Falvo II | 0167aaa | 2013-07-16 12:36:25 -0700 | [diff] [blame] | 162 | 	} | 
| Samuel A. Falvo II | d1ee798 | 2013-06-26 14:32:45 -0700 | [diff] [blame] | 163 | 	return access, err | 
| Samuel A. Falvo II | 1d3fa66 | 2013-06-25 15:29:32 -0700 | [diff] [blame] | 164 | } | 
| Samuel A. Falvo II | 2e2b877 | 2013-07-04 15:40:15 -0700 | [diff] [blame] | 165 |  | 
| Samuel A. Falvo II | 9e64f6b | 2013-07-16 14:26:50 -0700 | [diff] [blame] | 166 | // Authenticate() grants access to the OpenStack-compatible provider API. | 
 | 167 | // | 
 | 168 | // Providers are identified through a unique key string. | 
 | 169 | // See the RegisterProvider() method for more details. | 
 | 170 | // | 
 | 171 | // The supplied AuthOptions instance allows the client to specify only those credentials | 
 | 172 | // relevant for the authentication request.  At present, support exists for OpenStack | 
 | 173 | // Identity V2 API only; support for V3 will become available as soon as documentation for it | 
 | 174 | // becomes readily available. | 
 | 175 | // | 
 | 176 | // For Identity V2 API requirements, you must provide at least the Username and Password | 
 | 177 | // options.  The TenantId field is optional, and defaults to "". | 
 | 178 | func (c *Context) Authenticate(provider string, options AuthOptions) (*Access, error) { | 
 | 179 | 	p, err := c.ProviderByName(provider) | 
 | 180 | 	if err != nil { | 
 | 181 | 		return nil, err | 
 | 182 | 	} | 
 | 183 | 	return c.papersPlease(p, options) | 
 | 184 | } | 
 | 185 |  | 
 | 186 | // Reauthenticate attempts to reauthenticate using the configured access credentials, if | 
 | 187 | // allowed.  This method takes no action unless your AuthOptions has the AllowReauth flag | 
 | 188 | // set to true. | 
 | 189 | func (a *Access) Reauthenticate() error { | 
 | 190 | 	var other *Access | 
 | 191 | 	var err error | 
 | 192 |  | 
| Samuel A. Falvo II | 9e64f6b | 2013-07-16 14:26:50 -0700 | [diff] [blame] | 193 | 	if a.options.AllowReauth { | 
 | 194 | 		other, err = a.context.papersPlease(a.provider, a.options) | 
| Samuel A. Falvo II | fb58669 | 2013-07-16 17:00:14 -0700 | [diff] [blame] | 195 | 		if err == nil { | 
| Samuel A. Falvo II | 9e64f6b | 2013-07-16 14:26:50 -0700 | [diff] [blame] | 196 | 			*a = *other | 
 | 197 | 		} | 
 | 198 | 	} | 
 | 199 | 	return err | 
 | 200 | } | 
 | 201 |  | 
| Samuel A. Falvo II | 2e2b877 | 2013-07-04 15:40:15 -0700 | [diff] [blame] | 202 | // See AccessProvider interface definition for details. | 
 | 203 | func (a *Access) FirstEndpointUrlByCriteria(ac ApiCriteria) string { | 
 | 204 | 	ep := FindFirstEndpointByCriteria(a.ServiceCatalog, ac) | 
 | 205 | 	urls := []string{ep.PublicURL, ep.InternalURL} | 
 | 206 | 	return urls[ac.UrlChoice] | 
 | 207 | } | 
| Samuel A. Falvo II | bc0d54a | 2013-07-08 14:45:21 -0700 | [diff] [blame] | 208 |  | 
 | 209 | // See AccessProvider interface definition for details. | 
 | 210 | func (a *Access) AuthToken() string { | 
 | 211 | 	return a.Token.Id | 
 | 212 | } | 
| Samuel A. Falvo II | 659e14b | 2013-07-16 12:04:54 -0700 | [diff] [blame] | 213 |  | 
 | 214 | // See AccessProvider interface definition for details. | 
 | 215 | func (a *Access) Revoke(tok string) error { | 
| Samuel A. Falvo II | 0167aaa | 2013-07-16 12:36:25 -0700 | [diff] [blame] | 216 | 	url := a.provider.AuthEndpoint + "/" + tok | 
 | 217 | 	err := perigee.Delete(url, perigee.Options{ | 
 | 218 | 		MoreHeaders: map[string]string{ | 
 | 219 | 			"X-Auth-Token": a.AuthToken(), | 
 | 220 | 		}, | 
 | 221 | 		OkCodes: []int{204}, | 
 | 222 | 	}) | 
 | 223 | 	return err | 
| Samuel A. Falvo II | 659e14b | 2013-07-16 12:04:54 -0700 | [diff] [blame] | 224 | } | 
| Samuel A. Falvo II | dbc4e9e | 2013-11-19 14:39:37 -0800 | [diff] [blame] | 225 |  | 
 | 226 | // See ServiceCatalogerForIdentityV2 interface definition for details. | 
 | 227 | // Note that the raw slice is returend; be careful not to alter the fields of any members, | 
 | 228 | // for other components of Gophercloud may depend upon them. | 
 | 229 | // If this becomes a problem in the future, | 
 | 230 | // a future revision may return a deep-copy of the service catalog instead. | 
 | 231 | func (a *Access) V2ServiceCatalog() []CatalogEntry { | 
 | 232 | 	return a.ServiceCatalog | 
 | 233 | } |