gopkg.in/goose.v2@v2.0.1/testservices/identityservice/userpass.go (about) 1 package identityservice 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 9 "gopkg.in/goose.v2/testservices/hook" 10 ) 11 12 // Implement the v2 User Pass form of identity (Keystone) 13 14 type ErrorResponse struct { 15 Message string `json:"message"` 16 Code int `json:"code"` 17 Title string `json:"title"` 18 } 19 20 type ErrorWrapper struct { 21 Error ErrorResponse `json:"error"` 22 } 23 24 type UserPassRequest struct { 25 Auth struct { 26 PasswordCredentials struct { 27 Username string `json:"username"` 28 Password string `json:"password"` 29 } `json:"passwordCredentials"` 30 TenantName string `json:"tenantName"` 31 } `json:"auth"` 32 } 33 34 type Endpoint struct { 35 AdminURL string `json:"adminURL"` 36 InternalURL string `json:"internalURL"` 37 PublicURL string `json:"publicURL"` 38 Region string `json:"region"` 39 } 40 41 type V2Service struct { 42 Name string `json:"name"` 43 Type string `json:"type"` 44 Endpoints []Endpoint 45 } 46 47 type TokenResponse struct { 48 Expires string `json:"expires"` // should this be a date object? 49 Id string `json:"id"` // Actual token string 50 Tenant struct { 51 Id string `json:"id"` 52 Name string `json:"name"` 53 Description *string `json:"description"` 54 } `json:"tenant"` 55 } 56 57 type RoleResponse struct { 58 Id string `json:"id"` 59 Name string `json:"name"` 60 TenantId string `json:"tenantId"` 61 } 62 63 type UserResponse struct { 64 Id string `json:"id"` 65 Name string `json:"name"` 66 Roles []RoleResponse `json:"roles"` 67 } 68 69 type AccessResponse struct { 70 Access struct { 71 ServiceCatalog []V2Service `json:"serviceCatalog"` 72 Token TokenResponse `json:"token"` 73 User UserResponse `json:"user"` 74 } `json:"access"` 75 } 76 77 // Taken from: http://docs.openstack.org/api/quick-start/content/index.html#Getting-Credentials-a00665 78 var exampleResponse = `{ 79 "access": { 80 "serviceCatalog": [ 81 { 82 "endpoints": [ 83 { 84 "adminURL": "https://nova-api.trystack.org:9774/v1.1/1", 85 "internalURL": "https://nova-api.trystack.org:9774/v1.1/1", 86 "publicURL": "https://nova-api.trystack.org:9774/v1.1/1", 87 "region": "RegionOne" 88 } 89 ], 90 "name": "nova", 91 "type": "compute" 92 }, 93 { 94 "endpoints": [ 95 { 96 "adminURL": "https://GLANCE_API_IS_NOT_DISCLOSED/v1.1/1", 97 "internalURL": "https://GLANCE_API_IS_NOT_DISCLOSED/v1.1/1", 98 "publicURL": "https://GLANCE_API_IS_NOT_DISCLOSED/v1.1/1", 99 "region": "RegionOne" 100 } 101 ], 102 "name": "glance", 103 "type": "image" 104 }, 105 { 106 "endpoints": [ 107 { 108 "adminURL": "https://nova-api.trystack.org:5443/v2.0", 109 "internalURL": "https://keystone.trystack.org:5000/v2.0", 110 "publicURL": "https://keystone.trystack.org:5000/v2.0", 111 "region": "RegionOne" 112 } 113 ], 114 "name": "keystone", 115 "type": "identity" 116 } 117 ], 118 "token": { 119 "expires": "2012-02-15T19:32:21", 120 "id": "5df9d45d-d198-4222-9b4c-7a280aa35666", 121 "tenant": { 122 "id": "1", 123 "name": "admin", 124 "description": null 125 } 126 }, 127 "user": { 128 "id": "14", 129 "name": "annegentle", 130 "roles": [ 131 { 132 "id": "2", 133 "name": "Member", 134 "tenantId": "1" 135 } 136 ] 137 } 138 } 139 }` 140 141 type UserPass struct { 142 hook.TestService 143 Users 144 services []V2Service 145 } 146 147 func NewUserPass() *UserPass { 148 userpass := &UserPass{ 149 services: make([]V2Service, 0), 150 } 151 userpass.users = make(map[string]UserInfo) 152 userpass.tenants = make(map[string]string) 153 return userpass 154 } 155 156 func (u *UserPass) RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider) { 157 service := V2Service{name, serviceType, serviceProvider.Endpoints()} 158 u.AddService(Service{V2: service}) 159 } 160 161 func (u *UserPass) AddService(service Service) { 162 u.services = append(u.services, service.V2) 163 } 164 165 var internalError = []byte(`{ 166 "error": { 167 "message": "Internal failure", 168 "code": 500, 169 "title": Internal Server Error" 170 } 171 }`) 172 173 func (u *UserPass) ReturnFailure(w http.ResponseWriter, status int, message string) { 174 e := ErrorWrapper{ 175 Error: ErrorResponse{ 176 Message: message, 177 Code: status, 178 Title: http.StatusText(status), 179 }, 180 } 181 if content, err := json.Marshal(e); err != nil { 182 w.Header().Set("Content-Length", fmt.Sprintf("%d", len(internalError))) 183 w.WriteHeader(http.StatusInternalServerError) 184 w.Write(internalError) 185 } else { 186 w.Header().Set("Content-Length", fmt.Sprintf("%d", len(content))) 187 w.WriteHeader(status) 188 w.Write(content) 189 } 190 } 191 192 // Taken from an actual responses, however it may vary based on actual Openstack implementation 193 const ( 194 notJSON = ("Expecting to find application/json in Content-Type header." + 195 " The server could not comply with the request since it is either malformed" + 196 " or otherwise incorrect. The client is assumed to be in error.") 197 ) 198 199 func (u *UserPass) ServeHTTP(w http.ResponseWriter, r *http.Request) { 200 var req UserPassRequest 201 // Testing against Canonistack, all responses are application/json, even failures 202 w.Header().Set("Content-Type", "application/json") 203 if r.Header.Get("Content-Type") != "application/json" { 204 u.ReturnFailure(w, http.StatusBadRequest, notJSON) 205 return 206 } 207 if content, err := ioutil.ReadAll(r.Body); err != nil { 208 w.WriteHeader(http.StatusBadRequest) 209 return 210 } else { 211 if err := json.Unmarshal(content, &req); err != nil { 212 u.ReturnFailure(w, http.StatusBadRequest, notJSON) 213 return 214 } 215 } 216 userInfo, errmsg := u.authenticate(req.Auth.PasswordCredentials.Username, req.Auth.PasswordCredentials.Password, "default") 217 if errmsg != "" { 218 u.ReturnFailure(w, http.StatusUnauthorized, errmsg) 219 return 220 } 221 res, err := u.generateAccessResponse(userInfo) 222 if err != nil { 223 u.ReturnFailure(w, http.StatusInternalServerError, err.Error()) 224 return 225 } 226 content, err := json.Marshal(res) 227 if err != nil { 228 u.ReturnFailure(w, http.StatusInternalServerError, err.Error()) 229 return 230 } 231 w.WriteHeader(http.StatusOK) 232 w.Write(content) 233 } 234 235 func (u *UserPass) generateAccessResponse(userInfo *UserInfo) (*AccessResponse, error) { 236 res := AccessResponse{} 237 // We pre-populate the response with genuine entries so that it looks sane. 238 // XXX: We should really build up valid state for this instead, at the 239 // very least, we should manage the URLs better. 240 if err := json.Unmarshal([]byte(exampleResponse), &res); err != nil { 241 return nil, err 242 } 243 res.Access.ServiceCatalog = u.services 244 res.Access.Token.Id = userInfo.Token 245 res.Access.Token.Tenant.Id = userInfo.TenantId 246 res.Access.User.Id = userInfo.Id 247 if err := u.ProcessControlHook("authorisation", u, &res, userInfo); err != nil { 248 return nil, err 249 } 250 return &res, nil 251 } 252 253 // setupHTTP attaches all the needed handlers to provide the HTTP API. 254 func (u *UserPass) SetupHTTP(mux *http.ServeMux) { 255 mux.Handle("/tokens", u) 256 } 257 258 func (u *UserPass) Stop() { 259 // noop 260 }