Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

Commit

Permalink
Merge pull request #15 from pijalu/master
Browse files Browse the repository at this point in the history
Implemented Secure File API
  • Loading branch information
sdford authored Jun 28, 2018
2 parents 7bd67c2 + a2ced63 commit 3b45649
Show file tree
Hide file tree
Showing 8 changed files with 474 additions and 19 deletions.
26 changes: 26 additions & 0 deletions api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,29 @@ type SDBMetadata struct {
UserGroupPermissions map[string]string `json:"user_group_permissions"`
IAMRolePermissions map[string]string `json:"iam_role_permissions"`
}

// SecureFileSummary represents the metadata of a specific secure-file
type SecureFileSummary struct {
Name string `json:"name"`
Path string `json:"path"`
Size int `json:"size_in_bytes"`
SDBID string `json:"sdbox_id"`
Created time.Time `json:"created_ts"`
CreatedBy string `json:"created_by"`
LastUpdated time.Time `json:"last_updated_ts"`
LastUpdatedBy string `json:"last_updated_by"`
UserGroupPermissions map[string]string `json:"user_group_permissions"`
IAMRolePermissions map[string]string `json:"iam_role_permissions"`
}

// SecureFilesResponse is an object that wraps a list of SecureFileSummary for convenience with pagination
type SecureFilesResponse struct {
HasNext bool `json:"has_next"`
NextOffset int `json:"next_offset"`
Limit int
Offset int

ResultCount int `json:"file_count_in_result"`
TotalCount int `json:"total_file_count"`
Summaries []SecureFileSummary `json:"secure_file_summaries"`
}
4 changes: 4 additions & 0 deletions cerberus/category.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ var categoryBasePath = "/v1/category"
// List returns a list of roles that can be granted
func (r *Category) List() ([]*api.Category, error) {
resp, err := r.c.DoRequest(http.MethodGet, categoryBasePath, map[string]string{}, nil)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("Error while trying to get categories: %v", err)
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Error while trying to GET categories. Got HTTP status code %d", resp.StatusCode)
}
Expand Down
56 changes: 37 additions & 19 deletions cerberus/cerberus.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,19 @@ func (c *Client) Metadata() *Metadata {
}
}

// SecureFile returns the SecureFile client
func (c *Client) SecureFile() *SecureFile {
return &SecureFile{
c: c,
}
}

// ErrorBodyNotReturned is an error indicating that the server did not return error details (in case of a non-successful status).
// This likely means that there is some sort of server error that is occurring
var ErrorBodyNotReturned = fmt.Errorf("No error body returned from server")

// DoRequest is used to perform an HTTP request with the given method and path
// This method is what is called by other parts of the client and is exposed for advanced usage
func (c *Client) DoRequest(method, path string, params map[string]string, data interface{}) (*http.Response, error) {
// DoRequestWithBody executes a request with provided body
func (c *Client) DoRequestWithBody(method, path string, params map[string]string, contentType string, body io.Reader) (*http.Response, error) {
// Get a copy of the base URL and add the path
var baseURL = *c.CerberusURL
baseURL.Path = path
Expand All @@ -117,18 +123,8 @@ func (c *Client) DoRequest(method, path string, params map[string]string, data i
baseURL.RawQuery = p.Encode()
var req *http.Request
var err error
if data == nil {
req, err = http.NewRequest(method, baseURL.String(), nil)
} else {
// Encode the body to send in the request if one was given
body := &bytes.Buffer{}
err := json.NewEncoder(body).Encode(data)
if err != nil {
return nil, err
}
req, err = http.NewRequest(method, baseURL.String(), body)
}

req, err = http.NewRequest(method, baseURL.String(), body)
if err != nil {
return nil, err
}
Expand All @@ -137,9 +133,16 @@ func (c *Client) DoRequest(method, path string, params map[string]string, data i
return nil, headerErr
}
req.Header = headers

// Add content type if present
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}

resp, respErr := c.httpClient.Do(req)
if respErr != nil {
return nil, respErr
// We may get an actual response for redirect error
return resp, respErr
}
// Cerberus uses a refresh token header. If that header is sent with a value of "true,"
// refresh the token before returning
Expand All @@ -157,14 +160,29 @@ func (c *Client) DoRequest(method, path string, params map[string]string, data i
return resp, nil
}

// DoRequest is used to perform an HTTP request with the given method and path
// This method is what is called by other parts of the client and is exposed for advanced usage
func (c *Client) DoRequest(method, path string, params map[string]string, data interface{}) (*http.Response, error) {
var body io.ReadWriter
var contentType string

if data != nil {
body = &bytes.Buffer{}
contentType = "application/json"
err := json.NewEncoder(body).Encode(data)
if err != nil {
return nil, err
}
}

return c.DoRequestWithBody(method, path, params, contentType, body)
}

// parseResponse marshals the given body into the given interface. It should be used just like
// json.Marshal in that you pass a pointer to the function.
func parseResponse(r io.Reader, parseTo interface{}) error {
// Decode the body into the provided interface
if err := json.NewDecoder(r).Decode(parseTo); err != nil {
return err
}
return nil
return json.NewDecoder(r).Decode(parseTo)
}

// handleAPIError is a helper for parsing an error response body from the API.
Expand Down
4 changes: 4 additions & 0 deletions cerberus/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ func (m *Metadata) List(opts MetadataOpts) (*api.MetadataResponse, error) {
params["limit"] = fmt.Sprintf("%d", opts.Limit)
params["offset"] = fmt.Sprintf("%d", opts.Offset)
resp, err := m.c.DoRequest(http.MethodGet, metadataBasePath, params, nil)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("Error while trying to get roles: %v", err)
}

// Check if it is a bad request (improperly set params)
if resp.StatusCode == http.StatusBadRequest {
// Return the API error to the user
Expand Down
4 changes: 4 additions & 0 deletions cerberus/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ var roleBasePath = "/v1/role"
// List returns a list of roles that can be granted
func (r *Role) List() ([]*api.Role, error) {
resp, err := r.c.DoRequest(http.MethodGet, roleBasePath, map[string]string{}, nil)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("Error while trying to get roles: %v", err)
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Error while trying to GET roles. Got HTTP status code %d", resp.StatusCode)
}
Expand Down
20 changes: 20 additions & 0 deletions cerberus/sdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,13 @@ func (s *SDB) Get(id string) (*api.SafeDepositBox, error) {
}
returnedSDB := &api.SafeDepositBox{}
resp, err := s.c.DoRequest(http.MethodGet, sdbBasePath+"/"+id, map[string]string{}, nil)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("Error while trying to get SDB: %v", err)
}

if resp.StatusCode == http.StatusNotFound {
return nil, ErrorSafeDepositBoxNotFound
}
Expand All @@ -82,9 +86,13 @@ func (s *SDB) Get(id string) (*api.SafeDepositBox, error) {
func (s *SDB) List() ([]*api.SafeDepositBox, error) {
sdbList := []*api.SafeDepositBox{}
resp, err := s.c.DoRequest(http.MethodGet, sdbBasePath, map[string]string{}, nil)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("Error while trying to list SDB: %v", err)
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Error while trying to GET SDB list. Got HTTP status code %d", resp.StatusCode)
}
Expand All @@ -100,9 +108,13 @@ func (s *SDB) Create(newSDB *api.SafeDepositBox) (*api.SafeDepositBox, error) {
// Create the object we are returning
createdSDB := &api.SafeDepositBox{}
resp, err := s.c.DoRequest(http.MethodPost, sdbBasePath, map[string]string{}, newSDB)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("Error while creating SDB: %v", err)
}

if resp.StatusCode == http.StatusBadRequest {
// Return the API error to the user
return nil, handleAPIError(resp.Body)
Expand Down Expand Up @@ -133,9 +145,13 @@ func (s *SDB) Update(id string, updatedSDB *api.SafeDepositBox) (*api.SafeDeposi
}
returnedSDB := &api.SafeDepositBox{}
resp, err := s.c.DoRequest(http.MethodPut, sdbBasePath+"/"+id, map[string]string{}, updatedSDB)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("Error while updating SDB: %v", err)
}

if resp.StatusCode == http.StatusNotFound {
return nil, ErrorSafeDepositBoxNotFound
}
Expand Down Expand Up @@ -166,9 +182,13 @@ func (s *SDB) Delete(id string) error {
return ErrorSafeDepositBoxNotFound
}
resp, err := s.c.DoRequest(http.MethodDelete, sdbBasePath+"/"+id, map[string]string{}, nil)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return fmt.Errorf("Error while deleting SDB: %v", err)
}

if resp.StatusCode == http.StatusNotFound {
return ErrorSafeDepositBoxNotFound
}
Expand Down
149 changes: 149 additions & 0 deletions cerberus/securefile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
Copyright 2017 Nike Inc.
Licensed under the Apache License, Version 2.0 (the License);
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an AS IS BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cerberus

import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"path"

"github.com/Nike-Inc/cerberus-go-client/api"
)

// SecureFile is a subclient for secure files
type SecureFile struct {
c *Client
}

var secureFileBasePath = "/v1/secure-file"
var secureFileListBasePath = "/v1/secure-files"

// List returns a list of secure files
func (r *SecureFile) List(rootpath string) (*api.SecureFilesResponse, error) {
resp, err := r.c.DoRequest(http.MethodGet,
// path.Join will remove last '/' but cerberus expect a / suffix => Let's add it
path.Join(secureFileListBasePath, rootpath)+"/",
map[string]string{
"list": "true",
},
nil)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("error while trying to get secure files: %v", err)
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("error while trying to list secure files. Got HTTP status code %d",
resp.StatusCode)
}
sfr := &api.SecureFilesResponse{}
err = parseResponse(resp.Body, sfr)
if err != nil {
return nil, err
}
return sfr, nil
}

// Get downloads a secure file under localfile. File will be saved in output
func (r *SecureFile) Get(secureFilePath string, output io.Writer) error {
resp, err := r.c.DoRequest(http.MethodGet,
path.Join(secureFileBasePath, secureFilePath),
map[string]string{},
nil)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return fmt.Errorf("error while downloading secure file: %v", err)
}

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("error while trying to download secure file %s. Got HTTP status code %d",
secureFilePath,
resp.StatusCode)
}

// Copy
_, err = io.Copy(output, resp.Body)
if err != nil {
return err
}

return nil
}

// getUploadFileBodyWriter create a reader containing an encoded multipart file. It returns a reader, a content-type and/or possible error
func getUploadFileBodyWriter(filename string, input io.Reader) (io.Reader, string, error) {
// Create mpart
var b bytes.Buffer
w := multipart.NewWriter(&b)

part, err := w.CreateFormFile("file-content", filename)
if err != nil {
return nil, "", err
}
// Copy file
if _, err := io.Copy(part, input); err != nil {
return nil, "", err
}

// save content type of the body
contentType := w.FormDataContentType()

// close to flush mpart
if err := w.Close(); err != nil {
return nil, "", err
}

return &b, contentType, nil
}

// Put uploads a secure file to a given location localfile
func (r *SecureFile) Put(secureFilePath string, filename string, input io.Reader) error {
// Create multipart body and content type
body, contentType, err := getUploadFileBodyWriter(filename, input)
if err != nil {
return fmt.Errorf("error creating upload body: %v", err)
}

// Send request
resp, err := r.c.DoRequestWithBody(http.MethodPost,
path.Join(secureFileBasePath, secureFilePath),
map[string]string{},
contentType,
body)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return fmt.Errorf("error while downloading secure file: %v", err)
}

// expected sucess reply is "no content"
if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("error while trying to download secure file %s. Got HTTP status code %d",
secureFilePath,
resp.StatusCode)
}

return nil
}
Loading

0 comments on commit 3b45649

Please sign in to comment.