@@ -3,7 +3,6 @@ package coderd
3
3
import (
4
4
"context"
5
5
"crypto/ed25519"
6
- "fmt"
7
6
"net/http"
8
7
"sync"
9
8
"time"
@@ -15,11 +14,14 @@ import (
15
14
16
15
"cdr.dev/slog"
17
16
"github.com/coder/coder/coderd"
17
+ agplaudit "github.com/coder/coder/coderd/audit"
18
18
"github.com/coder/coder/coderd/httpapi"
19
19
"github.com/coder/coder/coderd/httpmw"
20
+ "github.com/coder/coder/coderd/workspacequota"
20
21
"github.com/coder/coder/codersdk"
21
22
"github.com/coder/coder/enterprise/audit"
22
23
"github.com/coder/coder/enterprise/audit/backends"
24
+ "github.com/coder/coder/enterprise/coderd/license"
23
25
)
24
26
25
27
// New constructs an Enterprise coderd API instance.
@@ -34,19 +36,8 @@ func New(ctx context.Context, options *Options) (*API, error) {
34
36
}
35
37
ctx , cancelFunc := context .WithCancel (ctx )
36
38
api := & API {
37
- AGPL : coderd .New (options .Options ),
38
- Options : options ,
39
-
40
- entitlements : entitlements {
41
- activeUsers : codersdk.Feature {
42
- Entitlement : codersdk .EntitlementNotEntitled ,
43
- Enabled : false ,
44
- },
45
- auditLogs : codersdk .EntitlementNotEntitled ,
46
- browserOnly : codersdk .EntitlementNotEntitled ,
47
- scim : codersdk .EntitlementNotEntitled ,
48
- workspaceQuota : codersdk .EntitlementNotEntitled ,
49
- },
39
+ AGPL : coderd .New (options .Options ),
40
+ Options : options ,
50
41
cancelEntitlementsLoop : cancelFunc ,
51
42
}
52
43
oauthConfigs := & httpmw.OAuth2Configs {
@@ -117,16 +108,7 @@ type API struct {
117
108
118
109
cancelEntitlementsLoop func ()
119
110
entitlementsMu sync.RWMutex
120
- entitlements entitlements
121
- }
122
-
123
- type entitlements struct {
124
- hasLicense bool
125
- activeUsers codersdk.Feature
126
- auditLogs codersdk.Entitlement
127
- browserOnly codersdk.Entitlement
128
- scim codersdk.Entitlement
129
- workspaceQuota codersdk.Entitlement
111
+ entitlements codersdk.Entitlements
130
112
}
131
113
132
114
func (api * API ) Close () error {
@@ -135,94 +117,57 @@ func (api *API) Close() error {
135
117
}
136
118
137
119
func (api * API ) updateEntitlements (ctx context.Context ) error {
138
- licenses , err := api .Database .GetUnexpiredLicenses (ctx )
139
- if err != nil {
140
- return err
141
- }
142
120
api .entitlementsMu .Lock ()
143
121
defer api .entitlementsMu .Unlock ()
144
- now := time .Now ()
145
122
146
- // Default all entitlements to be disabled.
147
- entitlements := entitlements {
148
- hasLicense : false ,
149
- activeUsers : codersdk.Feature {
150
- Enabled : false ,
151
- Entitlement : codersdk .EntitlementNotEntitled ,
152
- },
153
- auditLogs : codersdk .EntitlementNotEntitled ,
154
- scim : codersdk .EntitlementNotEntitled ,
155
- browserOnly : codersdk .EntitlementNotEntitled ,
156
- workspaceQuota : codersdk .EntitlementNotEntitled ,
123
+ entitlements , err := license .Entitlements (ctx , api .Database , api .Logger , api .Keys , map [string ]bool {
124
+ codersdk .FeatureAuditLog : api .AuditLogging ,
125
+ codersdk .FeatureBrowserOnly : api .BrowserOnly ,
126
+ codersdk .FeatureSCIM : len (api .SCIMAPIKey ) != 0 ,
127
+ codersdk .FeatureWorkspaceQuota : api .UserWorkspaceQuota != 0 ,
128
+ })
129
+ if err != nil {
130
+ return err
157
131
}
158
132
159
- // Here we loop through licenses to detect enabled features.
160
- for _ , l := range licenses {
161
- claims , err := validateDBLicense (l , api .Keys )
162
- if err != nil {
163
- api .Logger .Debug (ctx , "skipping invalid license" ,
164
- slog .F ("id" , l .ID ), slog .Error (err ))
165
- continue
133
+ featureChanged := func (featureName string ) (changed bool , enabled bool ) {
134
+ if api .entitlements .Features == nil {
135
+ return true , entitlements .Features [featureName ].Enabled
166
136
}
167
- entitlements .hasLicense = true
168
- entitlement := codersdk .EntitlementEntitled
169
- if now .After (claims .LicenseExpires .Time ) {
170
- // if the grace period were over, the validation fails, so if we are after
171
- // LicenseExpires we must be in grace period.
172
- entitlement = codersdk .EntitlementGracePeriod
173
- }
174
- if claims .Features .UserLimit > 0 {
175
- entitlements .activeUsers = codersdk.Feature {
176
- Enabled : true ,
177
- Entitlement : entitlement ,
178
- }
179
- currentLimit := int64 (0 )
180
- if entitlements .activeUsers .Limit != nil {
181
- currentLimit = * entitlements .activeUsers .Limit
182
- }
183
- limit := max (currentLimit , claims .Features .UserLimit )
184
- entitlements .activeUsers .Limit = & limit
185
- }
186
- if claims .Features .AuditLog > 0 {
187
- entitlements .auditLogs = entitlement
188
- }
189
- if claims .Features .BrowserOnly > 0 {
190
- entitlements .browserOnly = entitlement
191
- }
192
- if claims .Features .SCIM > 0 {
193
- entitlements .scim = entitlement
194
- }
195
- if claims .Features .WorkspaceQuota > 0 {
196
- entitlements .workspaceQuota = entitlement
137
+ oldFeature := api .entitlements .Features [featureName ]
138
+ newFeature := entitlements .Features [featureName ]
139
+ if oldFeature .Enabled != newFeature .Enabled {
140
+ return true , newFeature .Enabled
197
141
}
142
+ return false , newFeature .Enabled
198
143
}
199
144
200
- if entitlements .auditLogs != api .entitlements .auditLogs {
201
- // A flag could be added to the options that would allow disabling
202
- // enhanced audit logging here!
203
- if entitlements .auditLogs != codersdk .EntitlementNotEntitled && api .AuditLogging {
204
- auditor := audit .NewAuditor (
145
+ if changed , enabled := featureChanged (codersdk .FeatureAuditLog ); changed {
146
+ auditor := agplaudit .NewNop ()
147
+ if enabled {
148
+ auditor = audit .NewAuditor (
205
149
audit .DefaultFilter ,
206
150
backends .NewPostgres (api .Database , true ),
207
151
backends .NewSlog (api .Logger ),
208
152
)
209
- api .AGPL .Auditor .Store (& auditor )
210
153
}
154
+ api .AGPL .Auditor .Store (& auditor )
211
155
}
212
156
213
- if entitlements . browserOnly != api . entitlements . browserOnly {
157
+ if changed , enabled := featureChanged ( codersdk . FeatureBrowserOnly ); changed {
214
158
var handler func (rw http.ResponseWriter ) bool
215
- if entitlements . browserOnly != codersdk . EntitlementNotEntitled && api . BrowserOnly {
159
+ if enabled {
216
160
handler = api .shouldBlockNonBrowserConnections
217
161
}
218
162
api .AGPL .WorkspaceClientCoordinateOverride .Store (& handler )
219
163
}
220
164
221
- if entitlements . workspaceQuota != api . entitlements . workspaceQuota {
222
- if entitlements . workspaceQuota != codersdk . EntitlementNotEntitled && api . UserWorkspaceQuota > 0 {
223
- enforcer := NewEnforcer ( api . Options . UserWorkspaceQuota )
224
- api .AGPL . WorkspaceQuotaEnforcer . Store ( & enforcer )
165
+ if changed , enabled := featureChanged ( codersdk . FeatureWorkspaceQuota ); changed {
166
+ enforcer := workspacequota . NewNop ()
167
+ if enabled {
168
+ enforcer = NewEnforcer ( api .Options . UserWorkspaceQuota )
225
169
}
170
+ api .AGPL .WorkspaceQuotaEnforcer .Store (& enforcer )
226
171
}
227
172
228
173
api .entitlements = entitlements
@@ -235,82 +180,7 @@ func (api *API) serveEntitlements(rw http.ResponseWriter, r *http.Request) {
235
180
api .entitlementsMu .RLock ()
236
181
entitlements := api .entitlements
237
182
api .entitlementsMu .RUnlock ()
238
-
239
- resp := codersdk.Entitlements {
240
- Features : make (map [string ]codersdk.Feature ),
241
- Warnings : make ([]string , 0 ),
242
- HasLicense : entitlements .hasLicense ,
243
- Experimental : api .Experimental ,
244
- }
245
-
246
- if entitlements .activeUsers .Limit != nil {
247
- activeUserCount , err := api .Database .GetActiveUserCount (ctx )
248
- if err != nil {
249
- httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
250
- Message : "Unable to query database" ,
251
- Detail : err .Error (),
252
- })
253
- return
254
- }
255
- entitlements .activeUsers .Actual = & activeUserCount
256
- if activeUserCount > * entitlements .activeUsers .Limit {
257
- resp .Warnings = append (resp .Warnings ,
258
- fmt .Sprintf (
259
- "Your deployment has %d active users but is only licensed for %d." ,
260
- activeUserCount , * entitlements .activeUsers .Limit ))
261
- }
262
- }
263
- resp .Features [codersdk .FeatureUserLimit ] = entitlements .activeUsers
264
-
265
- // Audit logs
266
- resp .Features [codersdk .FeatureAuditLog ] = codersdk.Feature {
267
- Entitlement : entitlements .auditLogs ,
268
- Enabled : api .AuditLogging ,
269
- }
270
- // Audit logging is enabled by default. We don't want to display
271
- // a warning if they don't have a license.
272
- if entitlements .hasLicense && api .AuditLogging {
273
- if entitlements .auditLogs == codersdk .EntitlementNotEntitled {
274
- resp .Warnings = append (resp .Warnings ,
275
- "Audit logging is enabled but your license is not entitled to this feature." )
276
- }
277
- if entitlements .auditLogs == codersdk .EntitlementGracePeriod {
278
- resp .Warnings = append (resp .Warnings ,
279
- "Audit logging is enabled but your license for this feature is expired." )
280
- }
281
- }
282
-
283
- resp .Features [codersdk .FeatureBrowserOnly ] = codersdk.Feature {
284
- Entitlement : entitlements .browserOnly ,
285
- Enabled : api .BrowserOnly ,
286
- }
287
- if api .BrowserOnly {
288
- if entitlements .browserOnly == codersdk .EntitlementNotEntitled {
289
- resp .Warnings = append (resp .Warnings ,
290
- "Browser only connections are enabled but your license is not entitled to this feature." )
291
- }
292
- if entitlements .browserOnly == codersdk .EntitlementGracePeriod {
293
- resp .Warnings = append (resp .Warnings ,
294
- "Browser only connections are enabled but your license for this feature is expired." )
295
- }
296
- }
297
-
298
- resp .Features [codersdk .FeatureWorkspaceQuota ] = codersdk.Feature {
299
- Entitlement : entitlements .workspaceQuota ,
300
- Enabled : api .UserWorkspaceQuota > 0 ,
301
- }
302
- if api .UserWorkspaceQuota > 0 {
303
- if entitlements .workspaceQuota == codersdk .EntitlementNotEntitled {
304
- resp .Warnings = append (resp .Warnings ,
305
- "Workspace quotas are enabled but your license is not entitled to this feature." )
306
- }
307
- if entitlements .workspaceQuota == codersdk .EntitlementGracePeriod {
308
- resp .Warnings = append (resp .Warnings ,
309
- "Workspace quotas are enabled but your license for this feature is expired." )
310
- }
311
- }
312
-
313
- httpapi .Write (ctx , rw , http .StatusOK , resp )
183
+ httpapi .Write (ctx , rw , http .StatusOK , entitlements )
314
184
}
315
185
316
186
func (api * API ) runEntitlementsLoop (ctx context.Context ) {
@@ -374,10 +244,3 @@ func (api *API) runEntitlementsLoop(ctx context.Context) {
374
244
}
375
245
}
376
246
}
377
-
378
- func max (a , b int64 ) int64 {
379
- if a > b {
380
- return a
381
- }
382
- return b
383
- }
0 commit comments