195 lines
5 KiB
Go
195 lines
5 KiB
Go
package restapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/cookiejar"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
type HttpClientFactory func() *http.Client
|
|
|
|
// BaseAPIResponse represents the most basic standard Centrify API response,
|
|
// where Result itself is left as raw json
|
|
type BaseAPIResponse struct {
|
|
Success bool `json:"success"`
|
|
Result json.RawMessage
|
|
Message string
|
|
}
|
|
|
|
type StringResponse struct {
|
|
BaseAPIResponse
|
|
Result string
|
|
}
|
|
|
|
type BoolResponse struct {
|
|
BaseAPIResponse
|
|
Result bool
|
|
}
|
|
|
|
// GenericMapResponse represents Centrify API responses where results are map[string]interface{},
|
|
// this type allows direct access to these without further decoding.
|
|
type GenericMapResponse struct {
|
|
BaseAPIResponse
|
|
Result map[string]interface{}
|
|
}
|
|
|
|
// BackendType is the type of backend that is being implemented
|
|
type RestClientMode uint32
|
|
|
|
// RestClient represents a stateful API client (cookies maintained between calls, single service etc)
|
|
type RestClient struct {
|
|
Service string
|
|
Client *http.Client
|
|
Headers map[string]string
|
|
SourceHeader string
|
|
}
|
|
|
|
// GetNewRestClient creates a new RestClient for the specified endpoint. If a factory for creating
|
|
// http.Client's is not provided, you'll get a new: &http.Client{}.
|
|
func GetNewRestClient(service string, httpFactory HttpClientFactory) (*RestClient, error) {
|
|
jar, err := cookiejar.New(nil)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Munge on the service a little bit, force it to have no trailing / and always start with https://
|
|
url, err := url.Parse(service)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
url.Scheme = "https"
|
|
url.Path = ""
|
|
|
|
client := &RestClient{}
|
|
client.Service = url.String()
|
|
if httpFactory != nil {
|
|
client.Client = httpFactory()
|
|
} else {
|
|
client.Client = &http.Client{}
|
|
}
|
|
client.Client.Jar = jar
|
|
client.Headers = make(map[string]string)
|
|
client.SourceHeader = "cloud-golang-sdk"
|
|
return client, nil
|
|
}
|
|
|
|
func (r *RestClient) CallRawAPI(method string, args map[string]interface{}) ([]byte, error) {
|
|
return r.postAndGetBody(method, args)
|
|
}
|
|
|
|
func (r *RestClient) CallBaseAPI(method string, args map[string]interface{}) (*BaseAPIResponse, error) {
|
|
body, err := r.postAndGetBody(method, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bodyToBaseAPIResponse(body)
|
|
}
|
|
|
|
func (r *RestClient) CallGenericMapAPI(method string, args map[string]interface{}) (*GenericMapResponse, error) {
|
|
body, err := r.postAndGetBody(method, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bodyToGenericMapResponse(body)
|
|
}
|
|
|
|
func (r *RestClient) CallStringAPI(method string, args map[string]interface{}) (*StringResponse, error) {
|
|
body, err := r.postAndGetBody(method, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bodyToStringResponse(body)
|
|
}
|
|
|
|
func (r *RestClient) CallBoolAPI(method string, args map[string]interface{}) (*BoolResponse, error) {
|
|
body, err := r.postAndGetBody(method, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return bodyToBoolResponse(body)
|
|
}
|
|
|
|
func (r *RestClient) postAndGetBody(method string, args map[string]interface{}) ([]byte, error) {
|
|
service := strings.TrimSuffix(r.Service, "/")
|
|
method = strings.TrimPrefix(method, "/")
|
|
postdata := strings.NewReader(payloadFromMap(args))
|
|
postreq, err := http.NewRequest("POST", service+"/"+method, postdata)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
postreq.Header.Add("Content-Type", "application/json")
|
|
postreq.Header.Add("X-CENTRIFY-NATIVE-CLIENT", "Yes")
|
|
postreq.Header.Add("X-CFY-SRC", r.SourceHeader)
|
|
|
|
for k, v := range r.Headers {
|
|
postreq.Header.Add(k, v)
|
|
}
|
|
|
|
httpresp, err := r.Client.Do(postreq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer httpresp.Body.Close()
|
|
|
|
if httpresp.StatusCode == 200 {
|
|
return ioutil.ReadAll(httpresp.Body)
|
|
}
|
|
|
|
body, _ := ioutil.ReadAll(httpresp.Body)
|
|
return nil, fmt.Errorf("POST to %s failed with code %d, body: %s", method, httpresp.StatusCode, body)
|
|
}
|
|
|
|
// This function converts a map[string]interface{} into json string
|
|
func payloadFromMap(input map[string]interface{}) string {
|
|
if input != nil {
|
|
p, _ := json.Marshal(input)
|
|
return string(p)
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func bodyToBaseAPIResponse(body []byte) (*BaseAPIResponse, error) {
|
|
reply := &BaseAPIResponse{}
|
|
err := json.Unmarshal(body, &reply)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to unmarshal BaseApiResponse from HTTP response: %v", err)
|
|
}
|
|
return reply, nil
|
|
}
|
|
|
|
func bodyToGenericMapResponse(body []byte) (*GenericMapResponse, error) {
|
|
reply := &GenericMapResponse{}
|
|
err := json.Unmarshal(body, &reply)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to unmarshal GenericMapResponse from HTTP response: %v", err)
|
|
}
|
|
return reply, nil
|
|
}
|
|
|
|
func bodyToStringResponse(body []byte) (*StringResponse, error) {
|
|
reply := &StringResponse{}
|
|
err := json.Unmarshal(body, &reply)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to unmarshal StringResponse from HTTP response: %v", err)
|
|
}
|
|
return reply, nil
|
|
}
|
|
|
|
func bodyToBoolResponse(body []byte) (*BoolResponse, error) {
|
|
reply := &BoolResponse{}
|
|
err := json.Unmarshal(body, &reply)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to unmarshal BoolResponse from HTTP response: %v", err)
|
|
}
|
|
return reply, nil
|
|
}
|