This repository has been archived by the owner on Nov 18, 2022. It is now read-only.
/
tool_provider.go
144 lines (119 loc) · 3.69 KB
/
tool_provider.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package lti
import (
"github.com/goalbook/goalbook-auth/auth/lti/hmacsha1"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
)
// Tool provider wraps a provided http.Request, parses the LTI headers and allows for access
// to standard LTI Header fields as well as validation of LTI request.
type LTIToolProvider struct {
LTIHeaders *LTIStdHeaders
LTIResponse *LTIStdResponse
ltiConsumerRequest *http.Request
requestProxyPath string
requestProxyScheme string
}
// Create a new LTIToolProvide and set the LTIHeaders attribute to
// the parsed http.Request payload. Accepts URL encoded forms
// per LTI 1.0 spec and additionally supports also JSON payloads.
func NewLTIToolProvider(r *http.Request) (*LTIToolProvider, error) {
var err error
provider := LTIToolProvider{
ltiConsumerRequest: r,
LTIResponse: <IStdResponse{},
}
err = r.ParseForm()
if err != nil {
return nil, err
}
contType := r.Header.Get("Content-Type")
if contType == "application/x-www-form-urlencoded" {
provider.LTIHeaders, err = parseUrlEncodedForm(r.Form)
} else if contType == "application/json" {
provider.LTIHeaders, err = parseJsonBody(r.Body)
} else {
return nil, errors.New(fmt.Sprintf("%s: %s", errBadContentType, contType))
}
if err != nil {
return nil, err
}
return &provider, nil
}
// Validate that the LTI request was signed with the provided consumer secret.
//
// TODO Implement nonce checking and better timestamp checking.
func (tp *LTIToolProvider) ValidateRequest(consumerSecret string, checkTimestamp, checkNonce bool) (bool, error) {
var err error
defer func() {
if err != nil {
tp.LTIResponse.LTIErrorMessage = errInvalidRequest.Error()
tp.LTIResponse.LTIErrorLog = err.Error()
}
}()
// First check OAuth Signature
req := tp.ltiConsumerRequest
// Create fully qualified URL
var requestUrl *url.URL
if tp.requestProxyPath != "" {
req.URL.Path = fmt.Sprintf("%s%s", tp.requestProxyPath, req.URL.Path)
}
if !req.URL.IsAbs() {
requestUrl = req.URL
if tp.requestProxyScheme != "" {
requestUrl.Scheme = tp.requestProxyScheme
} else if req.TLS == nil {
requestUrl.Scheme = "http"
} else {
requestUrl.Scheme = "https"
}
requestUrl.Host = req.Host
} else {
requestUrl = req.URL
}
reqStr := hmacsha1.RequestSignatureBaseString(req.Method, requestUrl.String(), req.Form)
if !hmacsha1.CheckMAC(reqStr, consumerSecret, "", tp.LTIHeaders.OAuthSignature) {
err = errLogInvalidSignature
return false, err
}
// Second verify that timestamp is withing acceptable range
if checkTimestamp {
tstamp := tp.LTIHeaders.OAuthTimestamp
if !acceptTimestamp(tstamp) {
err = errLogInvalidTimestamp
return false, err
}
}
// Third, make sure unique nonce
if checkNonce {
// TODO: Nonce verification
}
return true, nil
}
// If the LTI request provided a return URL, serialize the LTIResponse and create
// a return URL from it.
func (tp *LTIToolProvider) CreateReturnURL() (*url.URL, error) {
if tp.LTIHeaders == nil || tp.LTIHeaders.LaunchPresReturnURL == "" {
return nil, nil
}
urlParams, err := tp.LTIResponse.Serialize()
if err != nil {
return nil, err
}
returnUrl := tp.LTIHeaders.LaunchPresReturnURL + "?" + urlParams
return url.Parse(returnUrl)
}
// IF a request is being proxied passed, the original request host information is overwritten by
// the proxying host. Use this to correctly set the desired host.
//
// TODO remove this, pull proxy path from headers
func (tp *LTIToolProvider) SetProxyPathPrefix(proxyPath string) {
proxyPath = strings.TrimPrefix(proxyPath, "/")
proxyPath = strings.TrimSuffix(proxyPath, "/")
tp.requestProxyPath = proxyPath
}
func (tp *LTIToolProvider) SetProxyScheme(scheme string) {
tp.requestProxyScheme = scheme
}