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  }