forked from emitter-io/emitter
/
contract.go
273 lines (226 loc) · 8.38 KB
/
contract.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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
/**********************************************************************************
* Copyright (c) 2009-2017 Misakai Ltd.
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free Software
* Foundation, either version 3 of the License, or(at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see<http://www.gnu.org/licenses/>.
************************************************************************************/
package security
import (
"errors"
"fmt"
"sync"
"time"
"github.com/emitter-io/config"
"github.com/emitter-io/emitter/logging"
"github.com/emitter-io/emitter/network/http"
"github.com/emitter-io/emitter/security/usage"
"github.com/emitter-io/emitter/utils"
)
// The contract's state possible values.
const (
ContractStateUnknown = uint8(iota)
ContractStateAllowed
ContractStateRefused
)
// Contract represents an interface for a contract.
type Contract interface {
Validate(key Key) bool // Validate checks the security key with the contract.
Stats() usage.Meter // Gets the usage statistics.
}
// contract represents a contract (user account).
type contract struct {
ID uint32 `json:"id"` // Gets or sets the contract id.
MasterID uint16 `json:"master"` // Gets or sets the master id.
Signature uint32 `json:"sign"` // Gets or sets the signature of the contract.
State uint8 `json:"state"` // Gets or sets the state of the contract.
stats usage.Meter // Gets the usage stats.
}
// Validate validates the contract data against a key.
func (c *contract) Validate(key Key) bool {
return c.MasterID == key.Master() &&
c.Signature == key.Signature() &&
c.ID == key.Contract() &&
c.State == ContractStateAllowed
}
// Gets the usage statistics.
func (c *contract) Stats() usage.Meter {
return c.stats
}
// ContractProvider represents an interface for a contract provider.
type ContractProvider interface {
config.Provider
Create() (Contract, error)
Get(id uint32) (Contract, bool)
}
// ------------------------------------------------------------------------------------
// Assert interface compliance
var _ ContractProvider = new(HTTPContractProvider)
// NoopContractProvider does not provide a contract.
type NoopContractProvider struct{}
// NewNoopContractProvider creates a new no-op contract provider.
func NewNoopContractProvider() *NoopContractProvider {
return new(NoopContractProvider)
}
// Name returns the name of the provider.
func (p *NoopContractProvider) Name() string {
return "noop"
}
// Configure configures the provider.
func (p *NoopContractProvider) Configure(config map[string]interface{}) error {
return nil
}
// Create creates a contract, the SingleContractProvider way.
func (p *NoopContractProvider) Create() (Contract, error) {
return nil, errors.New("Noop contract provider can not create contracts")
}
// Get returns a ContractData fetched by its id.
func (p *NoopContractProvider) Get(id uint32) (Contract, bool) {
return nil, false
}
// ------------------------------------------------------------------------------------
// Assert interface compliance
var _ ContractProvider = new(SingleContractProvider)
// SingleContractProvider provides contracts on premise.
type SingleContractProvider struct {
owner *contract // The owner contract.
usage usage.Metering // The usage stats container.
}
// NewSingleContractProvider creates a new single contract provider.
func NewSingleContractProvider(license *License, metering usage.Metering) *SingleContractProvider {
p := new(SingleContractProvider)
p.owner = new(contract)
p.owner.MasterID = 1
p.owner.ID = license.Contract
p.owner.Signature = license.Signature
p.owner.State = ContractStateAllowed
p.usage = metering
p.owner.stats = p.usage.Get(license.Contract).(usage.Meter)
return p
}
// Name returns the name of the provider.
func (p *SingleContractProvider) Name() string {
return "single"
}
// Configure configures the provider.
func (p *SingleContractProvider) Configure(config map[string]interface{}) error {
return nil
}
// Create creates a contract, the SingleContractProvider way.
func (p *SingleContractProvider) Create() (Contract, error) {
return nil, errors.New("Single contract provider can not create contracts")
}
// Get returns a ContractData fetched by its id.
func (p *SingleContractProvider) Get(id uint32) (Contract, bool) {
if p.owner == nil || p.owner.ID != id {
return nil, false
}
return p.owner, true
}
// ------------------------------------------------------------------------------------
// Assert interface compliance
var _ ContractProvider = new(HTTPContractProvider)
// HTTPContractProvider provides contracts over http.
type HTTPContractProvider struct {
url string // The url to hit for the provider.
owner *contract // The owner contract.
cache *sync.Map // The cache for the contracts.
usage usage.Metering // The usage stats container.
http http.Client // The http client to use.
head []http.HeaderValue // The http headers to add with each request.
done chan bool // The closing channel.
}
// NewHTTPContractProvider creates a new single contract provider.
func NewHTTPContractProvider(license *License, metering usage.Metering) *HTTPContractProvider {
p := HTTPContractProvider{}
p.owner = new(contract)
p.owner.MasterID = 1
p.owner.ID = license.Contract
p.owner.Signature = license.Signature
p.cache = new(sync.Map)
p.usage = metering
p.done = make(chan bool)
return &p
}
// Name returns the name of the provider.
func (p *HTTPContractProvider) Name() string {
return "http"
}
// Configure configures the provider.
func (p *HTTPContractProvider) Configure(config map[string]interface{}) (err error) {
if config == nil {
return errors.New("Configuration was not provided for HTTP contract provider")
}
// Get the interval from the provider configuration
interval := 10 * time.Minute
if v, ok := config["interval"]; ok {
if i, ok := v.(float64); ok {
interval = time.Duration(i) * time.Millisecond
}
}
// Get the authorization header to add to the request
headers := []http.HeaderValue{http.NewHeader("Accept", "application/json")}
if v, ok := config["authorization"]; ok {
if header, ok := v.(string); ok {
headers = append(headers, http.NewHeader("Authorization", header))
}
}
// Get the url from the provider configuration
if url, ok := config["url"]; ok {
p.url = url.(string)
// Create a new HTTP client to use
p.http, err = http.NewClient(p.url, 10*time.Second)
p.head = headers
// Periodically refresh contracts
utils.Repeat(p.refresh, interval, p.done) // TODO: closing chan
return
}
return errors.New("The 'url' parameter was not provider in the configuration for HTTP contract provider")
}
// Create creates a contract, the HTTPContractProvider way.
func (p *HTTPContractProvider) Create() (Contract, error) {
return nil, errors.New("HTTP contract provider can not create contracts")
}
// Get returns a ContractData fetched by its id.
func (p *HTTPContractProvider) Get(id uint32) (Contract, bool) {
if c, ok := p.cache.Load(id); ok {
return c.(Contract), true
}
// Load or store again, since we might have concurrently update it meanwhile
if contract, ok := p.fetchContract(id); ok {
c, _ := p.cache.LoadOrStore(id, contract)
return c.(Contract), true
}
return nil, false
}
// Fetches a single contract from the underlying contract provider.
func (p *HTTPContractProvider) fetchContract(id uint32) (*contract, bool) {
c := new(contract)
_, err := p.http.Get(fmt.Sprintf("%s%d", p.url, id), c, p.head...)
if err != nil {
logging.LogError("contract", "fetching http contract", err)
return nil, false
}
if c.ID == 0 {
return nil, false
}
c.stats = p.usage.Get(id).(usage.Meter)
return c, true
}
// Refresh fetches all the contracts from the underlying contract provider.
func (p *HTTPContractProvider) refresh() {
p.cache.Range(func(k, v interface{}) bool {
if id, ok := k.(uint32); ok {
if contract, ok := p.fetchContract(id); ok {
p.cache.Store(id, contract)
}
}
return true
})
}