gopkg.in/goose.v2@v2.0.1/testservices/identityservice/v3userpass.go (about) 1 package identityservice 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "time" 9 10 "gopkg.in/goose.v2/testservices/hook" 11 ) 12 13 // V3UserPassRequest Implement the v3 User Pass form of identity (Keystone) 14 type V3UserPassRequest struct { 15 Auth struct { 16 Identity struct { 17 Methods []string `json:"methods"` 18 Password struct { 19 User struct { 20 Name string `json:"name"` 21 Password string `json:"password"` 22 Domain struct { 23 Name string `json:"name,omitempty"` 24 } `json:"domain"` 25 } `json:"user"` 26 } `json:"password"` 27 } `json:"identity"` 28 Scope struct { 29 Project struct { 30 Name string `json:"name"` 31 ID string `json:"id"` 32 Domain struct { 33 Name string `json:"name,omitempty"` 34 } `json:"domain,omitempty"` 35 } `json:"project"` 36 Domain struct { 37 Name string `json:"name,omitempty"` 38 } `json:"domain"` 39 } `json:"scope"` 40 } `json:"auth"` 41 } 42 43 // V3Endpoint represents endpoints to a Service 44 type V3Endpoint struct { 45 Interface string `json:"interface"` 46 RegionID string `json:"region_id"` 47 URL string `json:"url"` 48 } 49 50 // NewV3Endpoints returns an array of V3Endpoint for the given Region and the 51 // passed admin, internal and public URLs. 52 func NewV3Endpoints(adminURL, internalURL, publicURL, regionID string) []V3Endpoint { 53 var eps []V3Endpoint 54 if adminURL != "" { 55 eps = append(eps, V3Endpoint{ 56 RegionID: regionID, 57 Interface: "admin", 58 URL: adminURL, 59 }) 60 } 61 if internalURL != "" { 62 eps = append(eps, V3Endpoint{ 63 RegionID: regionID, 64 Interface: "internal", 65 URL: internalURL, 66 }) 67 } 68 if publicURL != "" { 69 eps = append(eps, V3Endpoint{ 70 RegionID: regionID, 71 Interface: "public", 72 URL: publicURL, 73 }) 74 } 75 return eps 76 77 } 78 79 // V3Service represents an OpenStack web service that you can access through a URL. 80 type V3Service struct { 81 ID string `json:"id"` 82 Name string `json:"name"` 83 Type string `json:"type"` 84 Endpoints []V3Endpoint `json:"endpoints"` 85 } 86 87 // V3TokenResponse repesent a Token returned as a response to authentication to 88 // keystone v3. 89 type V3TokenResponse struct { 90 Expires time.Time `json:"expires_at"` 91 Issued time.Time `json:"issued_at"` 92 Methods []string `json:"methods"` 93 Catalog []V3Service `json:"catalog,omitempty"` 94 Project *V3Project `json:"project,omitempty"` 95 Domain *V3Domain `json:"domain,omitempty"` 96 User struct { 97 ID string `json:"id"` 98 Name string `json:"name"` 99 } `json:"user"` 100 } 101 102 // V3Project represent an openstack project, A project is the base unit of ownership. 103 // Resources are owned by a specific project. A project is owned by a specific domain. 104 type V3Project struct { 105 ID string `json:"id,omitempty"` 106 Name string `json:"name,omitempty"` 107 } 108 109 // V3Domain represents an authentication domain. 110 type V3Domain struct { 111 ID string `json:"id,omitempty"` 112 Name string `json:"name,omitempty"` 113 } 114 115 // V3UserPass represents an authenticated user to a service. 116 type V3UserPass struct { 117 hook.TestService 118 Users 119 services []V3Service 120 } 121 122 // NewV3UserPass returns a new V3UserPass 123 func NewV3UserPass() *V3UserPass { 124 userpass := &V3UserPass{ 125 services: make([]V3Service, 0), 126 } 127 userpass.users = make(map[string]UserInfo) 128 userpass.tenants = make(map[string]string) 129 return userpass 130 } 131 132 // RegisterServiceProvider registers V3UserPass as a service provider. 133 func (u *V3UserPass) RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider) { 134 service := V3Service{ 135 ID: name, 136 Name: name, 137 Type: serviceType, 138 Endpoints: serviceProvider.V3Endpoints(), 139 } 140 u.AddService(Service{V3: service}) 141 } 142 143 // AddService adds a service to the current V3UserPass. 144 func (u *V3UserPass) AddService(service Service) { 145 u.services = append(u.services, service.V3) 146 } 147 148 // ReturnFailure wraps and returns an error through the http connection. 149 func (u *V3UserPass) ReturnFailure(w http.ResponseWriter, status int, message string) { 150 e := ErrorWrapper{ 151 Error: ErrorResponse{ 152 Message: message, 153 Code: status, 154 Title: http.StatusText(status), 155 }, 156 } 157 if content, err := json.Marshal(e); err != nil { 158 w.Header().Set("Content-Length", fmt.Sprintf("%d", len(internalError))) 159 w.WriteHeader(http.StatusInternalServerError) 160 w.Write(internalError) 161 } else { 162 w.Header().Set("Content-Length", fmt.Sprintf("%d", len(content))) 163 w.WriteHeader(status) 164 w.Write(content) 165 } 166 } 167 168 // ServeHTTP serves V3UserPass for testing purposes. 169 func (u *V3UserPass) ServeHTTP(w http.ResponseWriter, r *http.Request) { 170 var req V3UserPassRequest 171 // Testing against Canonistack, all responses are application/json, even failures 172 w.Header().Set("Content-Type", "application/json") 173 if r.Header.Get("Content-Type") != "application/json" { 174 u.ReturnFailure(w, http.StatusBadRequest, notJSON) 175 return 176 } 177 if content, err := ioutil.ReadAll(r.Body); err != nil { 178 w.WriteHeader(http.StatusBadRequest) 179 return 180 } else { 181 if err := json.Unmarshal(content, &req); err != nil { 182 u.ReturnFailure(w, http.StatusBadRequest, notJSON) 183 return 184 } 185 } 186 domain := req.Auth.Scope.Project.Domain.Name 187 if domain == "" { 188 domain = req.Auth.Scope.Domain.Name 189 } 190 if domain == "" { 191 domain = "default" 192 } 193 // validate the Auth structure 194 if err := u.ProcessControlHook("preauthentication", u, req); err != nil { 195 u.ReturnFailure(w, http.StatusInternalServerError, err.Error()) 196 } 197 198 userInfo, errmsg := u.authenticate( 199 req.Auth.Identity.Password.User.Name, 200 req.Auth.Identity.Password.User.Password, 201 domain, 202 ) 203 if errmsg != "" { 204 u.ReturnFailure(w, http.StatusUnauthorized, errmsg) 205 return 206 } 207 208 res, err := u.generateV3TokenResponse(userInfo) 209 if err != nil { 210 u.ReturnFailure(w, http.StatusInternalServerError, err.Error()) 211 return 212 } 213 if req.Auth.Scope.Project.Name != "" { 214 id, name := u.addTenant(req.Auth.Scope.Project.Name) 215 res.Project = &V3Project{ 216 ID: id, 217 Name: name, 218 } 219 } 220 if req.Auth.Scope.Domain.Name != "" { 221 res.Domain = &V3Domain{ 222 Name: req.Auth.Scope.Domain.Name, 223 } 224 } 225 content, err := json.Marshal(struct { 226 Token *V3TokenResponse `json:"token"` 227 }{ 228 Token: res, 229 }) 230 if err != nil { 231 u.ReturnFailure(w, http.StatusInternalServerError, err.Error()) 232 return 233 } 234 w.Header().Set("X-Subject-Token", userInfo.Token) 235 w.WriteHeader(http.StatusCreated) 236 w.Write(content) 237 } 238 239 func (u *V3UserPass) generateV3TokenResponse(userInfo *UserInfo) (*V3TokenResponse, error) { 240 res := V3TokenResponse{} 241 242 res.Issued = time.Now() 243 res.Expires = res.Issued.Add(24 * time.Hour) 244 res.Methods = []string{"password"} 245 res.Catalog = u.services 246 res.User.ID = userInfo.Id 247 248 return &res, nil 249 } 250 251 // SetupHTTP attaches all the needed handlers to provide the HTTP API. 252 func (u *V3UserPass) SetupHTTP(mux *http.ServeMux) { 253 mux.Handle("/v3/auth/tokens", u) 254 } 255 256 func (u *V3UserPass) Stop() { 257 // noop 258 }