@@ -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,17 +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
- trial bool
126
- activeUsers codersdk.Feature
127
- auditLogs codersdk.Entitlement
128
- browserOnly codersdk.Entitlement
129
- scim codersdk.Entitlement
130
- workspaceQuota codersdk.Entitlement
111
+ entitlements codersdk.Entitlements
131
112
}
132
113
133
114
func (api * API ) Close () error {
@@ -136,98 +117,57 @@ func (api *API) Close() error {
136
117
}
137
118
138
119
func (api * API ) updateEntitlements (ctx context.Context ) error {
139
- licenses , err := api .Database .GetUnexpiredLicenses (ctx )
140
- if err != nil {
141
- return err
142
- }
143
120
api .entitlementsMu .Lock ()
144
121
defer api .entitlementsMu .Unlock ()
145
- now := time .Now ()
146
122
147
- // Default all entitlements to be disabled.
148
- entitlements := entitlements {
149
- hasLicense : false ,
150
- activeUsers : codersdk.Feature {
151
- Enabled : false ,
152
- Entitlement : codersdk .EntitlementNotEntitled ,
153
- },
154
- auditLogs : codersdk .EntitlementNotEntitled ,
155
- scim : codersdk .EntitlementNotEntitled ,
156
- browserOnly : codersdk .EntitlementNotEntitled ,
157
- workspaceQuota : codersdk .EntitlementNotEntitled ,
158
- trial : true ,
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
159
131
}
160
132
161
- // Here we loop through licenses to detect enabled features.
162
- for _ , l := range licenses {
163
- claims , err := validateDBLicense (l , api .Keys )
164
- if err != nil {
165
- api .Logger .Debug (ctx , "skipping invalid license" ,
166
- slog .F ("id" , l .ID ), slog .Error (err ))
167
- continue
168
- }
169
- entitlements .hasLicense = true
170
- entitlement := codersdk .EntitlementEntitled
171
- if now .After (claims .LicenseExpires .Time ) {
172
- // if the grace period were over, the validation fails, so if we are after
173
- // LicenseExpires we must be in grace period.
174
- entitlement = codersdk .EntitlementGracePeriod
133
+ featureChanged := func (featureName string ) (changed bool , enabled bool ) {
134
+ if api .entitlements .Features == nil {
135
+ return true , entitlements .Features [featureName ].Enabled
175
136
}
176
- if claims .Features .UserLimit > 0 {
177
- entitlements .activeUsers = codersdk.Feature {
178
- Enabled : true ,
179
- Entitlement : entitlement ,
180
- }
181
- currentLimit := int64 (0 )
182
- if entitlements .activeUsers .Limit != nil {
183
- currentLimit = * entitlements .activeUsers .Limit
184
- }
185
- limit := max (currentLimit , claims .Features .UserLimit )
186
- entitlements .activeUsers .Limit = & limit
187
- }
188
- if claims .Features .AuditLog > 0 {
189
- entitlements .auditLogs = entitlement
190
- }
191
- if claims .Features .BrowserOnly > 0 {
192
- entitlements .browserOnly = entitlement
193
- }
194
- if claims .Features .SCIM > 0 {
195
- entitlements .scim = entitlement
196
- }
197
- if claims .Features .WorkspaceQuota > 0 {
198
- entitlements .workspaceQuota = entitlement
199
- }
200
- if ! claims .Trial {
201
- entitlements .trial = claims .Trial
137
+ oldFeature := api .entitlements .Features [featureName ]
138
+ newFeature := entitlements .Features [featureName ]
139
+ if oldFeature .Enabled != newFeature .Enabled {
140
+ return true , newFeature .Enabled
202
141
}
142
+ return false , newFeature .Enabled
203
143
}
204
144
205
- if entitlements .auditLogs != api .entitlements .auditLogs {
206
- // A flag could be added to the options that would allow disabling
207
- // enhanced audit logging here!
208
- if entitlements .auditLogs != codersdk .EntitlementNotEntitled && api .AuditLogging {
209
- auditor := audit .NewAuditor (
145
+ if changed , enabled := featureChanged (codersdk .FeatureAuditLog ); changed {
146
+ auditor := agplaudit .NewNop ()
147
+ if enabled {
148
+ auditor = audit .NewAuditor (
210
149
audit .DefaultFilter ,
211
150
backends .NewPostgres (api .Database , true ),
212
151
backends .NewSlog (api .Logger ),
213
152
)
214
- api .AGPL .Auditor .Store (& auditor )
215
153
}
154
+ api .AGPL .Auditor .Store (& auditor )
216
155
}
217
156
218
- if entitlements . browserOnly != api . entitlements . browserOnly {
157
+ if changed , enabled := featureChanged ( codersdk . FeatureBrowserOnly ); changed {
219
158
var handler func (rw http.ResponseWriter ) bool
220
- if entitlements . browserOnly != codersdk . EntitlementNotEntitled && api . BrowserOnly {
159
+ if enabled {
221
160
handler = api .shouldBlockNonBrowserConnections
222
161
}
223
162
api .AGPL .WorkspaceClientCoordinateOverride .Store (& handler )
224
163
}
225
164
226
- if entitlements . workspaceQuota != api . entitlements . workspaceQuota {
227
- if entitlements . workspaceQuota != codersdk . EntitlementNotEntitled && api . UserWorkspaceQuota > 0 {
228
- enforcer := NewEnforcer ( api . Options . UserWorkspaceQuota )
229
- 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 )
230
169
}
170
+ api .AGPL .WorkspaceQuotaEnforcer .Store (& enforcer )
231
171
}
232
172
233
173
api .entitlements = entitlements
@@ -240,83 +180,7 @@ func (api *API) serveEntitlements(rw http.ResponseWriter, r *http.Request) {
240
180
api .entitlementsMu .RLock ()
241
181
entitlements := api .entitlements
242
182
api .entitlementsMu .RUnlock ()
243
-
244
- resp := codersdk.Entitlements {
245
- Features : make (map [string ]codersdk.Feature ),
246
- Warnings : make ([]string , 0 ),
247
- HasLicense : entitlements .hasLicense ,
248
- Trial : entitlements .trial ,
249
- Experimental : api .Experimental ,
250
- }
251
-
252
- if entitlements .activeUsers .Limit != nil {
253
- activeUserCount , err := api .Database .GetActiveUserCount (ctx )
254
- if err != nil {
255
- httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
256
- Message : "Unable to query database" ,
257
- Detail : err .Error (),
258
- })
259
- return
260
- }
261
- entitlements .activeUsers .Actual = & activeUserCount
262
- if activeUserCount > * entitlements .activeUsers .Limit {
263
- resp .Warnings = append (resp .Warnings ,
264
- fmt .Sprintf (
265
- "Your deployment has %d active users but is only licensed for %d." ,
266
- activeUserCount , * entitlements .activeUsers .Limit ))
267
- }
268
- }
269
- resp .Features [codersdk .FeatureUserLimit ] = entitlements .activeUsers
270
-
271
- // Audit logs
272
- resp .Features [codersdk .FeatureAuditLog ] = codersdk.Feature {
273
- Entitlement : entitlements .auditLogs ,
274
- Enabled : api .AuditLogging ,
275
- }
276
- // Audit logging is enabled by default. We don't want to display
277
- // a warning if they don't have a license.
278
- if entitlements .hasLicense && api .AuditLogging {
279
- if entitlements .auditLogs == codersdk .EntitlementNotEntitled {
280
- resp .Warnings = append (resp .Warnings ,
281
- "Audit logging is enabled but your license is not entitled to this feature." )
282
- }
283
- if entitlements .auditLogs == codersdk .EntitlementGracePeriod {
284
- resp .Warnings = append (resp .Warnings ,
285
- "Audit logging is enabled but your license for this feature is expired." )
286
- }
287
- }
288
-
289
- resp .Features [codersdk .FeatureBrowserOnly ] = codersdk.Feature {
290
- Entitlement : entitlements .browserOnly ,
291
- Enabled : api .BrowserOnly ,
292
- }
293
- if api .BrowserOnly {
294
- if entitlements .browserOnly == codersdk .EntitlementNotEntitled {
295
- resp .Warnings = append (resp .Warnings ,
296
- "Browser only connections are enabled but your license is not entitled to this feature." )
297
- }
298
- if entitlements .browserOnly == codersdk .EntitlementGracePeriod {
299
- resp .Warnings = append (resp .Warnings ,
300
- "Browser only connections are enabled but your license for this feature is expired." )
301
- }
302
- }
303
-
304
- resp .Features [codersdk .FeatureWorkspaceQuota ] = codersdk.Feature {
305
- Entitlement : entitlements .workspaceQuota ,
306
- Enabled : api .UserWorkspaceQuota > 0 ,
307
- }
308
- if api .UserWorkspaceQuota > 0 {
309
- if entitlements .workspaceQuota == codersdk .EntitlementNotEntitled {
310
- resp .Warnings = append (resp .Warnings ,
311
- "Workspace quotas are enabled but your license is not entitled to this feature." )
312
- }
313
- if entitlements .workspaceQuota == codersdk .EntitlementGracePeriod {
314
- resp .Warnings = append (resp .Warnings ,
315
- "Workspace quotas are enabled but your license for this feature is expired." )
316
- }
317
- }
318
-
319
- httpapi .Write (ctx , rw , http .StatusOK , resp )
183
+ httpapi .Write (ctx , rw , http .StatusOK , entitlements )
320
184
}
321
185
322
186
func (api * API ) runEntitlementsLoop (ctx context.Context ) {
@@ -380,10 +244,3 @@ func (api *API) runEntitlementsLoop(ctx context.Context) {
380
244
}
381
245
}
382
246
}
383
-
384
- func max (a , b int64 ) int64 {
385
- if a > b {
386
- return a
387
- }
388
- return b
389
- }
0 commit comments