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  }