1
// Copyright 2019 Aporeto Inc.
2
// Licensed under the Apache License, Version 2.0 (the "License");
3
// you may not use this file except in compliance with the License.
4
// You may obtain a copy of the License at
5
//     http://www.apache.org/licenses/LICENSE-2.0
6
// Unless required by applicable law or agreed to in writing, software
7
// distributed under the License is distributed on an "AS IS" BASIS,
8
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
// See the License for the specific language governing permissions and
10
// limitations under the License.
11

12
package elemental
13

14
import (
15
	"crypto/tls"
16
	"fmt"
17
	"io/ioutil"
18
	"net/http"
19
	"strconv"
20
	"strings"
21

22
	"github.com/gofrs/uuid"
23
)
24

25
// A Request represents an abstract request on an elemental model.
26
type Request struct {
27
	RequestID            string
28
	Namespace            string
29
	Recursive            bool
30
	Operation            Operation
31
	Identity             Identity
32
	Order                []string
33
	ObjectID             string
34
	ParentIdentity       Identity
35
	ParentID             string
36
	Data                 []byte
37
	Parameters           Parameters
38
	Headers              http.Header
39
	Username             string
40
	Password             string
41
	Page                 int
42
	PageSize             int
43
	After                string
44
	Limit                int
45
	OverrideProtection   bool
46
	Version              int
47
	ExternalTrackingID   string
48
	ExternalTrackingType string
49
	ContentType          EncodingType
50
	Accept               EncodingType
51

52
	Metadata           map[string]interface{}
53
	ClientIP           string
54
	TLSConnectionState *tls.ConnectionState
55

56
	req *http.Request
57
}
58

59
// NewRequest returns a new Request.
60
func NewRequest() *Request {
61

62 14
	return &Request{
63 14
		RequestID:   uuid.Must(uuid.NewV4()).String(),
64 14
		Parameters:  Parameters{},
65 14
		Headers:     http.Header{},
66 14
		Metadata:    map[string]interface{}{},
67 14
		ContentType: EncodingTypeJSON,
68 14
		Accept:      EncodingTypeJSON,
69
	}
70
}
71

72
// NewRequestFromHTTPRequest returns a new Request from the given http.Request.
73
func NewRequestFromHTTPRequest(req *http.Request, manager ModelManager) (*Request, error) {
74

75 14
	if req.URL == nil || req.URL.String() == "" {
76 14
		return nil, NewError("Bad Request", "Request must have an url", "elemental", http.StatusBadRequest)
77
	}
78

79 14
	var operation Operation
80 14
	var identity Identity
81 14
	var parentIdentity Identity
82 14
	var ID string
83 14
	var parentID string
84 14
	var username string
85 14
	var password string
86 14
	var version int
87 14
	var data []byte
88 14
	var err error
89

90 14
	auth := strings.Split(req.Header.Get("Authorization"), " ")
91 14
	if len(auth) == 2 {
92 14
		username = auth[0]
93 14
		password = auth[1]
94
	}
95

96 14
	components := strings.Split(req.URL.Path, "/")
97

98
	// We remove the first element as it's always empty
99 14
	components = append(components[:0], components[1:]...)
100

101
	// If the first one is "v" it means the next one has to be a int for the version number.
102 14
	if components[0] == "v" {
103 14
		version, err = strconv.Atoi(components[1])
104 14
		if err != nil {
105 14
			return nil, NewError("Bad Request", fmt.Sprintf("Invalid api version number '%s'", components[1]), "elemental", http.StatusBadRequest)
106
		}
107
		// once we've set the version, we remove it, and continue as usual.
108 14
		components = append(components[:0], components[2:]...)
109
	}
110

111 14
	contentType, acceptType, err := EncodingFromHeaders(req.Header)
112 14
	if err != nil {
113 14
		return nil, err
114
	}
115

116 14
	switch len(components) {
117 14
	case 1:
118 14
		identity = manager.IdentityFromCategory(components[0])
119 14
	case 2:
120 14
		identity = manager.IdentityFromCategory(components[0])
121 14
		ID = components[1]
122 14
	case 3:
123 14
		parentIdentity = manager.IdentityFromCategory(components[0])
124 14
		parentID = components[1]
125 14
		identity = manager.IdentityFromCategory(components[2])
126 14
	default:
127 14
		return nil, NewError("Bad Request", fmt.Sprintf("%s is not a valid elemental request path", req.URL), "elemental", http.StatusBadRequest)
128
	}
129

130 14
	if parentIdentity.IsEmpty() {
131 14
		parentIdentity = RootIdentity
132
	}
133

134 14
	switch req.Method {
135 14
	case http.MethodDelete:
136 14
		operation = OperationDelete
137

138 14
	case http.MethodGet:
139 14
		if len(components) == 1 || len(components) == 3 {
140 14
			operation = OperationRetrieveMany
141 14
		} else {
142 14
			operation = OperationRetrieve
143
		}
144

145 14
	case http.MethodHead:
146 14
		operation = OperationInfo
147

148 14
	case http.MethodPatch:
149 14
		operation = OperationPatch
150 14
		if _, ok := externalSupportedContentType[string(contentType)]; !ok {
151 14
			data, err = ioutil.ReadAll(req.Body)
152 14
			if err != nil {
153 14
				return nil, NewError("Bad Request", fmt.Sprintf("Unable to read body of request: %s", err), "elemental", http.StatusBadRequest)
154
			}
155 14
			defer req.Body.Close() // nolint: errcheck
156
		}
157

158 14
	case http.MethodPost:
159 14
		operation = OperationCreate
160 14
		if _, ok := externalSupportedContentType[string(contentType)]; !ok {
161 14
			data, err = ioutil.ReadAll(req.Body)
162 14
			if err != nil {
163 14
				return nil, NewError("Bad Request", fmt.Sprintf("Unable to read body of request: %s", err), "elemental", http.StatusBadRequest)
164
			}
165 14
			defer req.Body.Close() // nolint: errcheck
166
		}
167

168 14
	case http.MethodPut:
169 14
		operation = OperationUpdate
170 14
		if _, ok := externalSupportedContentType[string(contentType)]; !ok {
171 14
			data, err = ioutil.ReadAll(req.Body)
172 14
			if err != nil {
173 14
				return nil, NewError("Bad Request", fmt.Sprintf("Unable to read body of request: %s", err), "elemental", http.StatusBadRequest)
174
			}
175 14
			defer req.Body.Close() // nolint: errcheck
176
		}
177
	}
178

179 14
	var page, pageSize, limit int
180 14
	var recursive, override bool
181 14
	var after string
182 14
	var order []string
183

184 14
	q := req.URL.Query()
185 14
	if v := q.Get("page"); v != "" {
186 14
		page, err = strconv.Atoi(v)
187 14
		if err != nil {
188 14
			return nil, NewError("Bad Request", "Parameter `page` must be an integer", "elemental", http.StatusBadRequest)
189
		}
190 14
		q.Del("page")
191
	}
192

193 14
	if v := q.Get("pagesize"); v != "" {
194 14
		pageSize, err = strconv.Atoi(v)
195 14
		if err != nil {
196 14
			return nil, NewError("Bad Request", "Parameter `pagesize` must be an integer", "elemental", http.StatusBadRequest)
197
		}
198 14
		q.Del("pagesize")
199
	}
200

201 14
	if v := q.Get("recursive"); v != "" {
202 14
		recursive = true
203 14
		q.Del("recursive")
204
	}
205

206 14
	if v := q.Get("override"); v != "" {
207 14
		override = true
208 14
		q.Del("override")
209
	}
210

211 14
	if v, ok := q["order"]; ok {
212
		for _, o := range v {
213 14
			if o == "" || o == "\u0000" {
214 14
				return nil, NewError("Bad Request", "Parameter `order` must be set when provided", "elemental", http.StatusBadRequest)
215
			}
216
		}
217 14
		order = v
218 14
		q.Del("order")
219
	}
220

221 14
	if v := q.Get("limit"); v != "" {
222 14
		limit, err = strconv.Atoi(v)
223 14
		if pageSize != 0 {
224 14
			return nil, NewError("Bad Request", "You cannot set 'limit' and 'pagesize' at the same time", "elemental", http.StatusBadRequest)
225
		}
226 14
		if err != nil {
227 14
			return nil, NewError("Bad Request", "Parameter `limit` must be an integer", "elemental", http.StatusBadRequest)
228
		}
229 14
		q.Del("limit")
230
	}
231

232 14
	if v := q.Get("after"); v != "" {
233 14
		if v == "" || v == "\u0000" {
234 14
			return nil, NewError("Bad Request", "Parameter `after` must be set when provided", "elemental", http.StatusBadRequest)
235
		}
236 14
		if len(order) > 1 {
237 14
			return nil, NewError("Bad Request", "You can only order on a single field when using 'after'", "elemental", http.StatusBadRequest)
238
		}
239 14
		if page != 0 {
240 14
			return nil, NewError("Bad Request", "You cannot set 'after' and 'page' at the same time", "elemental", http.StatusBadRequest)
241
		}
242 14
		after = v
243 14
		q.Del("after")
244
	}
245

246 14
	paramsMap := Parameters{}
247 14
	qKeys := map[string]struct{}{}
248
	for k := range q {
249 14
		qKeys[k] = struct{}{}
250
	}
251

252
	for _, pdef := range ParametersForOperation(manager.Relationships(), identity, parentIdentity, operation) {
253 14
		p, err := pdef.Parse(q[pdef.Name])
254 14
		if err != nil {
255 14
			return nil, err
256
		}
257 14
		delete(qKeys, pdef.Name)
258 14
		paramsMap[pdef.Name] = *p
259
	}
260

261 14
	if len(qKeys) > 0 {
262 14
		errs := NewErrors()
263
		for k := range qKeys {
264 14
			errs = errs.Append(NewError("Bad Request", fmt.Sprintf("Unknown parameter: `%s`", k), "elemental", http.StatusBadRequest))
265
		}
266 14
		return nil, errs
267
	}
268

269 14
	rel := RelationshipInfoForOperation(manager.Relationships(), identity, parentIdentity, operation)
270 14
	if rel != nil {
271 14
		if err := paramsMap.Validate(rel.RequiredParameters); err != nil {
272 14
			return nil, err
273
		}
274
	}
275

276 14
	var clientIP string
277
	// Parse the Forwarded header as they can be in form
278
	// X-Forwarded-For: <original_client>, [proxy1], [proxy2>
279 14
	if ip := strings.TrimSpace(strings.Split(req.Header.Get("X-Forwarded-For"), ",")[0]); ip != "" {
280 14
		clientIP = ip
281 14
	} else if ip := req.Header.Get("X-Real-IP"); ip != "" {
282 14
		clientIP = ip
283 14
	} else {
284 14
		clientIP = req.RemoteAddr
285
	}
286

287 14
	var namespace string
288 14
	if namespacer := GetNamespacer(); namespacer != nil {
289 14
		if namespace, err = namespacer.Extract(req); err != nil {
290 14
			return nil, err
291
		}
292
	}
293

294 14
	return &Request{
295 14
		RequestID:            uuid.Must(uuid.NewV4()).String(),
296 14
		Namespace:            namespace,
297 14
		Recursive:            recursive,
298 14
		Page:                 page,
299 14
		PageSize:             pageSize,
300 14
		After:                after,
301 14
		Limit:                limit,
302 14
		Operation:            operation,
303 14
		Identity:             identity,
304 14
		ObjectID:             ID,
305 14
		ParentID:             parentID,
306 14
		ParentIdentity:       parentIdentity,
307 14
		Parameters:           paramsMap,
308 14
		Username:             username,
309 14
		Password:             password,
310 14
		Data:                 data,
311 14
		TLSConnectionState:   req.TLS,
312 14
		Headers:              req.Header,
313 14
		OverrideProtection:   override,
314 14
		Metadata:             map[string]interface{}{},
315 14
		Version:              version,
316 14
		ExternalTrackingID:   req.Header.Get("X-External-Tracking-ID"),
317 14
		ExternalTrackingType: req.Header.Get("X-External-Tracking-Type"),
318 14
		Order:                order,
319 14
		ClientIP:             clientIP,
320 14
		ContentType:          contentType,
321 14
		Accept:               acceptType,
322 14
		req:                  req,
323 14
	}, nil
324
}
325

326
// Duplicate duplicates the Request.
327
func (r *Request) Duplicate() *Request {
328

329 14
	req := NewRequest()
330

331 14
	req.Namespace = r.Namespace
332 14
	req.Recursive = r.Recursive
333 14
	req.Page = r.Page
334 14
	req.PageSize = r.PageSize
335 14
	req.After = r.After
336 14
	req.Limit = r.Limit
337 14
	req.Operation = r.Operation
338 14
	req.Identity = r.Identity
339 14
	req.ObjectID = r.ObjectID
340 14
	req.ParentID = r.ParentID
341 14
	req.ParentIdentity = r.ParentIdentity
342 14
	req.Username = r.Username
343 14
	req.Password = r.Password
344 14
	req.Data = r.Data
345 14
	req.Version = r.Version
346 14
	req.OverrideProtection = r.OverrideProtection
347 14
	req.TLSConnectionState = r.TLSConnectionState
348 14
	req.ExternalTrackingID = r.ExternalTrackingID
349 14
	req.ExternalTrackingType = r.ExternalTrackingType
350 14
	req.ClientIP = r.ClientIP
351 14
	req.Order = append([]string{}, r.Order...)
352 14
	req.req = r.req
353 14
	req.ContentType = r.ContentType
354 14
	req.Accept = r.Accept
355

356
	for k, v := range r.Headers {
357 14
		req.Headers[k] = v
358
	}
359

360
	for k, v := range r.Parameters {
361 14
		req.Parameters[k] = v
362
	}
363

364
	for k, v := range r.Metadata {
365 14
		req.Metadata[k] = v
366
	}
367

368 14
	return req
369
}
370

371
// GetEncoding returns the encoding used to encode the body.
372
func (r *Request) GetEncoding() EncodingType {
373 14
	return r.ContentType
374
}
375

376
// Decode decodes the data into the given destination.
377
func (r *Request) Decode(dst interface{}) error {
378

379 14
	return Decode(r.GetEncoding(), r.Data, dst)
380
}
381

382
// HTTPRequest returns the native http.Request, if any.
383
func (r *Request) HTTPRequest() *http.Request {
384 14
	return r.req
385
}
386

387
func (r *Request) String() string {
388

389 14
	return fmt.Sprintf("<request id:%s operation:%s namespace:%s recursive:%v identity:%s objectid:%s parentidentity:%s parentid:%s version:%d>",
390 14
		r.RequestID,
391 14
		r.Operation,
392 14
		r.Namespace,
393 14
		r.Recursive,
394 14
		r.Identity,
395 14
		r.ObjectID,
396 14
		r.ParentIdentity,
397 14
		r.ParentID,
398 14
		r.Version,
399 14
	)
400
}

Read our documentation on viewing source code .

Loading