fce44ae9 Andy McCall 1 month ago ↶
1
2
3
use super :: format:: { short_id, signature_line, when} ;
75167672 Andy McCall 1 month ago
4
use super :: json;
fce44ae9 Andy McCall 1 month ago ↶
5
use super :: open;
135212de Andy McCall 1 month ago ↶
6
use super :: ui;
fce44ae9 Andy McCall 1 month ago ↶
7
use anyhow:: Result;
8
use oxy_core:: hash:: ChangeHash;
75167672 Andy McCall 1 month ago
9
use oxy_core:: hash:: KeyId;
83e0677b Andy McCall 1 month ago ↶
10
use oxy_core:: identity:: Identity;
28fc23d7 Andy McCall 1 month ago ↶
11
use oxy_core:: objects:: Hlc;
12
use oxy_core:: protocol:: SignedPolicy;
c6aa5bd9 Andy McCall 1 month ago ↶
13
use oxy_core:: repo:: { self , IdentityScope, PolicyUpdate, Repo, Rotation, SignatureStatus} ;
83e0677b Andy McCall 1 month ago ↶
14
use std:: path:: { Path, PathBuf} ;
fce44ae9 Andy McCall 1 month ago ↶
15
28fc23d7 Andy McCall 1 month ago ↶
16
const DAY_MS : u64 = 24 * 60 * 60 * 1000 ;
17
18
fn expiry_from_days ( days : u64 ) -> Option< u64 > {
19
( days > 0 ) .then ( | | Hlc:: now( ) .wall_ms + days * DAY_MS )
20
}
21
22
23
fn resolve_key ( repo : & Repo, prefix : & str ) -> Result< KeyId> {
24
if let Some ( k) = KeyId:: from_hex( prefix) {
25
return Ok ( k) ;
26
}
27
let mut candidates: Vec< KeyId> = Vec :: new( ) ;
28
if let Some ( ( _ , sp) ) = repo.current_policy ( ) ? {
29
candidates.extend ( sp.policy.identities.iter ( ) .map ( | i | i.hash ( ) ) ) ;
30
candidates.extend ( sp.policy.revoked.iter ( ) .copied ( ) ) ;
31
}
32
if let Some ( own) = repo.config.author_key {
33
candidates.push ( own) ;
34
if let Ok ( id) = repo.identity ( & own) {
35
candidates.extend ( id.delegates.iter ( ) .copied ( ) ) ;
36
}
37
}
38
candidates.sort ( ) ;
39
candidates.dedup ( ) ;
75167672 Andy McCall 1 month ago
40
let matches: Vec< _ > = candidates
41
.into_iter ( )
42
.filter ( | k | k.starts_with_hex ( prefix) )
43
.collect ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
44
match matches.len ( ) {
45
1 => Ok ( matches[ 0 ] ) ,
46
0 => anyhow:: bail! ( " no known key starts with {prefix:?}" ) ,
47
_ => anyhow:: bail! ( " {prefix:?} is ambiguous" ) ,
48
}
49
}
50
51
52
53
fn report_update ( u : & PolicyUpdate, out : Option< PathBuf> ) -> Result< ( ) > {
54
if u.applied {
75167672 Andy McCall 1 month ago
55
println! (
56
" {} policy serial {} {} " ,
57
ui:: done( " Applied" ) ,
58
u.policy.serial,
59
ui:: dim( " (push to publish it)" )
60
) ;
28fc23d7 Andy McCall 1 month ago ↶
61
return Ok ( ( ) ) ;
62
}
63
let path = out.unwrap_or_else ( | | PathBuf:: from( format! ( " policy-{} .proposal" , u.policy.serial) ) ) ;
64
std:: fs:: write( & path, u.signed ( ) .encode ( ) ) ? ;
65
println! (
66
" {} policy serial {} needs {} of {} root signatures; has {} . Wrote {} for another root to run {} " ,
67
ui:: caution( ) ,
68
u.policy.serial,
69
u.policy.root_threshold,
70
u.policy.roots.len ( ) ,
71
u.sigs.len ( ) ,
72
ui:: path( path.display ( ) ) ,
73
ui:: dim( format! ( " oxy policy sign {} " , path.display ( ) ) )
74
) ;
75
Ok ( ( ) )
76
}
77
78
fn report_rotation ( r : & Rotation, verb : & str ) -> Result< ( ) > {
75167672 Andy McCall 1 month ago
79
println! (
80
" {} identity {} <{} >" ,
81
ui:: done( verb) ,
82
ui:: author( & r.identity.name) ,
83
r.identity.email
84
) ;
28fc23d7 Andy McCall 1 month ago ↶
85
println! ( " {} " , ui:: kv( " old key" , ui:: dim( r.old.to_hex ( ) ) ) ) ;
86
println! ( " {} " , ui:: kv( " new key" , r.new.to_hex ( ) ) ) ;
87
if ! r.identity.delegates.is_empty ( ) {
75167672 Andy McCall 1 month ago
88
println! (
89
" {} " ,
90
ui:: kv(
91
" delegates" ,
92
r.identity
93
.delegates
94
.iter ( )
95
.map ( | d | d.short ( 12 ) )
96
.collect:: < Vec< _ > > ( )
97
.join ( " " )
98
)
99
) ;
28fc23d7 Andy McCall 1 month ago ↶
100
}
101
if let Some ( e) = r.identity.expires {
102
println! ( " {} " , ui:: kv( " expires" , when ( e) ) ) ;
103
}
104
match & r.policy {
105
Some ( u) => report_update ( u, None ) ? ,
75167672 Andy McCall 1 month ago
106
None => println! (
107
" {} {} " ,
108
ui:: note( ) ,
109
ui:: dim( " no policy names the old key; export the new identity for policy holders" )
110
) ,
28fc23d7 Andy McCall 1 month ago ↶
111
}
112
Ok ( ( ) )
113
}
114
75167672 Andy McCall 1 month ago
115
pub fn create (
116
name : Option< String> ,
117
email : Option< String> ,
118
local : bool ,
119
expires : Option< u64 > ,
120
) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
121
let mut repo = open ( ) ? ;
122
let name = name.unwrap_or_else ( | | repo.config.author_name.clone ( ) ) ;
123
let email = email.unwrap_or_else ( | | repo.config.author_email.clone ( ) ) ;
75167672 Andy McCall 1 month ago
124
let scope = if local {
125
IdentityScope:: Repo
126
} else {
127
IdentityScope:: User
128
} ;
129
let ( key, _ ) =
130
repo.create_identity_with ( & name, & email, scope, expires.and_then ( expiry_from_days) ) ? ;
131
println! (
132
" {} identity {} <{email} >" ,
133
ui:: done( " Created" ) ,
134
ui:: author( & name)
135
) ;
94084087 Andy McCall 1 month ago ↶
136
println! ( " {} " , ui:: kv( " key" , key) ) ;
75167672 Andy McCall 1 month ago
137
println! (
138
" {} " ,
139
ui:: kv( " secret" , ui:: path( repo:: default_keys_dir( ) .display ( ) ) )
140
) ;
fce44ae9 Andy McCall 1 month ago ↶
141
match scope {
75167672 Andy McCall 1 month ago
142
IdentityScope:: User => println! (
143
" {} " ,
144
ui:: kv(
145
" scope" ,
146
format! (
147
" default author in {} " ,
148
ui:: path( repo.user_config_path ( ) .display ( ) )
149
)
150
)
151
) ,
152
IdentityScope:: Repo => {
153
println! ( " {} " , ui:: kv( " scope" , " this repository's .oxy/config only" ) )
154
}
fce44ae9 Andy McCall 1 month ago ↶
155
}
156
if ! repo.config.sign_changes {
94084087 Andy McCall 1 month ago ↶
157
println! ( " {} {} " , ui:: note( ) , ui:: dim( " changes will carry this author; run oxy config --user core.sign change to sign each commit" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
158
}
159
Ok ( 0 )
160
}
161
162
pub fn show ( ) -> Result< i32 > {
163
let repo = open ( ) ? ;
164
let Some ( key) = repo.config.author_key else {
165
println! ( " No identity configured; run `oxy identity create`." ) ;
166
return Ok ( 0 ) ;
167
} ;
168
let id = repo.identity ( & key) ? ;
75167672 Andy McCall 1 month ago
169
println! (
170
" {} {} <{} >" ,
171
ui:: section( " identity" ) ,
172
ui:: author( & id.name) ,
173
id.email
174
) ;
94084087 Andy McCall 1 month ago ↶
175
println! ( " {} " , ui:: kv( " key" , key) ) ;
75167672 Andy McCall 1 month ago
176
println! (
177
" {} " ,
178
ui:: kv(
179
" created" ,
180
format! (
181
" {} {} " ,
182
when ( id.created) ,
183
ui:: dim( format! ( " ({} )" , ui:: ago( id.created) ) )
184
)
185
)
186
) ;
28fc23d7 Andy McCall 1 month ago ↶
187
if let Some ( e) = id.expires {
188
let expired = id.is_expired_at ( Hlc:: now( ) .wall_ms) ;
75167672 Andy McCall 1 month ago
189
println! (
190
" {} " ,
191
ui:: kv(
192
" expires" ,
193
if expired {
194
ui:: err( format! ( " {} EXPIRED" , when ( e) ) )
195
} else {
196
when ( e)
197
}
198
)
199
) ;
28fc23d7 Andy McCall 1 month ago ↶
200
}
201
if let Some ( p) = id.previous {
202
println! ( " {} " , ui:: kv( " previous" , ui:: dim( p.short ( 12 ) ) ) ) ;
203
}
204
if ! id.delegates.is_empty ( ) {
75167672 Andy McCall 1 month ago
205
println! (
206
" {} " ,
207
ui:: kv(
208
" delegates" ,
209
id.delegates
210
.iter ( )
211
.map ( | d | d.short ( 12 ) )
212
.collect:: < Vec< _ > > ( )
213
.join ( " " )
214
)
215
) ;
28fc23d7 Andy McCall 1 month ago ↶
216
}
217
if repo.is_revoked ( & key) ? {
75167672 Andy McCall 1 month ago
218
println! (
219
" {} " ,
220
ui:: kv(
221
" status" ,
222
ui:: err( format! ( " {} REVOKED" , ui:: glyph( " ✖" , " x" ) ) )
223
)
224
) ;
28fc23d7 Andy McCall 1 month ago ↶
225
}
75167672 Andy McCall 1 month ago
226
println! (
227
" {} " ,
228
ui:: kv(
229
" secret" ,
230
if repo.secret_key ( ) ? .is_some ( ) {
231
ui:: ok( format! ( " {} available" , ui:: glyph( " ✔" , " *" ) ) )
232
} else {
233
ui:: err( format! (
234
" {} not found in the key directory" ,
235
ui:: glyph( " ✖" , " x" )
236
) )
237
}
238
)
239
) ;
240
println! (
241
" {} " ,
242
ui:: kv(
243
" signing" ,
244
if repo.config.sign_changes {
245
" every change (core.sign = change)"
246
} else {
247
" at push via seals (core.sign = seal)"
248
}
249
)
250
) ;
fce44ae9 Andy McCall 1 month ago ↶
251
Ok ( 0 )
252
}
253
c6aa5bd9 Andy McCall 1 month ago ↶
254
pub fn verify ( change : Option< & str > , as_json : bool ) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
255
let repo = open ( ) ? ;
256
let hashes: Vec< ChangeHash> = match change {
257
Some ( c) => vec! [ repo.resolve_change ( c) ? ] ,
75167672 Andy McCall 1 month ago
258
None => repo
259
.log ( ) ?
260
.into_iter ( )
261
.filter ( | e | ! e.is_working_copy)
262
.map ( | e | e.hash )
263
.collect ( ) ,
fce44ae9 Andy McCall 1 month ago ↶
264
} ;
265
let mut bad = 0 ;
c6aa5bd9 Andy McCall 1 month ago ↶
266
if as_json {
267
let mut items = Vec :: new( ) ;
268
for h in & hashes {
75167672 Andy McCall 1 month ago
269
let Ok ( c) = repo.store ( ) .get_change ( h) else {
270
continue ;
271
} ;
c6aa5bd9 Andy McCall 1 month ago ↶
272
let ( status, key, name, revoked, reason) = match repo.authenticate_change ( h) ? {
273
SignatureStatus:: Unsigned => ( " unsigned" , None , None , false , None ) ,
75167672 Andy McCall 1 month ago
274
SignatureStatus:: Valid {
275
key,
276
name,
277
sealed,
278
revoked,
279
..
280
} => (
281
if sealed { " sealed" } else { " signed" } ,
282
Some ( key.to_hex ( ) ) ,
283
Some ( name) ,
284
revoked,
285
None ,
286
) ,
c6aa5bd9 Andy McCall 1 month ago ↶
287
SignatureStatus:: Invalid( why) => {
288
bad + = 1 ;
289
( " invalid" , None , None , false , Some ( why) )
290
}
291
} ;
292
items.push ( json:: obj( [
293
( " hash" , json:: s( & h.to_hex ( ) ) ) ,
294
( " change_id" , json:: s( & c.change_id.to_hex ( ) ) ) ,
295
( " status" , json:: s( status) ) ,
296
( " key" , json:: opt( key.as_deref ( ) , json:: s) ) ,
297
( " name" , json:: opt( name.as_deref ( ) , json:: s) ) ,
298
( " revoked" , json:: b( revoked) ) ,
299
( " reason" , json:: opt( reason.as_deref ( ) , json:: s) ) ,
300
] ) ) ;
301
}
302
println! ( " {} " , json:: arr( items) ) ;
303
return Ok ( if bad > 0 { 1 } else { 0 } ) ;
304
}
fce44ae9 Andy McCall 1 month ago ↶
305
for h in hashes {
2863456a Andy McCall 1 month ago ↶
306
let Ok ( c) = repo.store ( ) .get_change ( & h) else {
75167672 Andy McCall 1 month ago
307
println! (
308
" {} {} " ,
309
ui:: hash( format! ( " #{} " , h.short ( 12 ) ) ) ,
310
ui:: dim( " not present (shallow)" )
311
) ;
2863456a Andy McCall 1 month ago ↶
312
continue ;
313
} ;
fce44ae9 Andy McCall 1 month ago ↶
314
let line = signature_line ( & repo, & h) ? ;
135212de Andy McCall 1 month ago ↶
315
let styled = if line.starts_with ( " INVALID" ) {
fce44ae9 Andy McCall 1 month ago ↶
316
bad + = 1 ;
94084087 Andy McCall 1 month ago ↶
317
ui:: err( format! ( " {} {line} " , ui:: glyph( " ✖" , " x" ) ) )
135212de Andy McCall 1 month ago ↶
318
} else if line = = " unsigned" {
94084087 Andy McCall 1 month ago ↶
319
ui:: dim( format! ( " {} {line} " , ui:: glyph( " ·" , " -" ) ) )
135212de Andy McCall 1 month ago ↶
320
} else {
94084087 Andy McCall 1 month ago ↶
321
ui:: ok( format! ( " {} {line} " , ui:: glyph( " ✔" , " v" ) ) )
135212de Andy McCall 1 month ago ↶
322
} ;
75167672 Andy McCall 1 month ago
323
println! (
324
" {} {} {styled} " ,
325
ui:: id( short_id ( & c) ) ,
326
ui:: hash( format! ( " #{} " , h.short ( 12 ) ) )
327
) ;
fce44ae9 Andy McCall 1 month ago ↶
328
}
329
Ok ( if bad > 0 { 1 } else { 0 } )
330
}
331
332
pub fn config ( key : & str , value : & str , user : bool ) -> Result< i32 > {
333
if user {
334
let path = repo:: default_user_config( ) ;
335
repo:: Config:: set_user( & path, key, value) ? ;
75167672 Andy McCall 1 month ago
336
println! (
337
" {} {key} = {value} {} " ,
338
ui:: done( " Set" ) ,
339
ui:: dim( format! ( " (in {} )" , path.display ( ) ) )
340
) ;
fce44ae9 Andy McCall 1 month ago ↶
341
} else {
342
let mut repo = open ( ) ? ;
343
repo.config.set ( key, value) ? ;
344
repo.save_config ( ) ? ;
75167672 Andy McCall 1 month ago
345
println! (
346
" {} {key} = {value} {} " ,
347
ui:: done( " Set" ) ,
348
ui:: dim( " (in this repository)" )
349
) ;
fce44ae9 Andy McCall 1 month ago ↶
350
}
351
Ok ( 0 )
352
}
83e0677b Andy McCall 1 month ago ↶
353
354
pub fn export ( path : Option< PathBuf> ) -> Result< i32 > {
355
let repo = open ( ) ? ;
356
let Some ( key) = repo.config.author_key else {
357
anyhow:: bail! ( " no identity configured; run `oxy identity create`" ) ;
358
} ;
359
let id = repo.identity ( & key) ? ;
360
let path = path.unwrap_or_else ( | | PathBuf:: from( format! ( " {} .identity" , key.short ( 16 ) ) ) ) ;
361
std:: fs:: write( & path, id.encode ( ) ) ? ;
75167672 Andy McCall 1 month ago
362
println! (
363
" {} identity {} <{} > ({} ) to {} " ,
364
ui:: done( " Wrote" ) ,
365
ui:: author( & id.name) ,
366
id.email,
367
key.short ( 12 ) ,
368
ui:: path( path.display ( ) )
369
) ;
83e0677b Andy McCall 1 month ago ↶
370
Ok ( 0 )
371
}
372
28fc23d7 Andy McCall 1 month ago ↶
373
pub fn rotate ( ) -> Result< i32 > {
374
let mut repo = open ( ) ? ;
375
let r = repo.rotate_identity ( ) ? ;
376
report_rotation ( & r, " Rotated" ) ? ;
377
Ok ( 0 )
378
}
379
380
pub fn renew ( expires : u64 ) -> Result< i32 > {
381
let mut repo = open ( ) ? ;
382
let r = repo.renew_identity ( expiry_from_days ( expires) ) ? ;
383
report_rotation ( & r, " Renewed" ) ? ;
384
Ok ( 0 )
385
}
386
387
pub fn revoke ( key : Option< & str > , reason : & str ) -> Result< i32 > {
388
let repo = open ( ) ? ;
389
let key = match key {
390
Some ( k) => resolve_key ( & repo, k) ? ,
75167672 Andy McCall 1 month ago
391
None => repo
392
.config
393
.author_key
394
.ok_or_else ( | | anyhow:: anyhow! ( " no identity configured" ) ) ? ,
28fc23d7 Andy McCall 1 month ago ↶
395
} ;
396
let update = repo.revoke_identity ( & key, reason) ? ;
75167672 Andy McCall 1 month ago
397
println! (
398
" {} key {} {} " ,
399
ui:: done( " Revoked" ) ,
400
ui:: hash( key.short ( 12 ) ) ,
401
if reason.is_empty ( ) {
402
String :: new( )
403
} else {
404
format! ( " : {reason} " )
405
}
406
) ;
28fc23d7 Andy McCall 1 month ago ↶
407
match update {
408
Some ( u) => report_update ( & u, None ) ? ,
75167672 Andy McCall 1 month ago
409
None => println! (
410
" {} {} " ,
411
ui:: note( ) ,
412
ui:: dim( " recorded locally; no policy named the key" )
413
) ,
28fc23d7 Andy McCall 1 month ago ↶
414
}
415
if repo.config.author_key = = Some ( key) {
75167672 Andy McCall 1 month ago
416
println! (
417
" {} {} " ,
418
ui:: caution( ) ,
419
ui:: warn( " this was your configured key; run oxy identity create or rotate" )
420
) ;
28fc23d7 Andy McCall 1 month ago ↶
421
}
422
Ok ( 0 )
423
}
424
425
pub fn delegate_add ( identity_file : & Path) -> Result< i32 > {
426
let mut repo = open ( ) ? ;
427
let id = Identity:: decode( & std:: fs:: read( identity_file) ? ) ? ;
428
let r = repo.delegate_add ( id) ? ;
429
report_rotation ( & r, " Delegated" ) ? ;
430
Ok ( 0 )
431
}
432
433
pub fn delegate_remove ( key : & str ) -> Result< i32 > {
434
let mut repo = open ( ) ? ;
435
let k = resolve_key ( & repo, key) ? ;
436
let r = repo.delegate_remove ( & k) ? ;
437
report_rotation ( & r, " Undelegated" ) ? ;
438
Ok ( 0 )
439
}
440
441
pub fn delegate_list ( ) -> Result< i32 > {
442
let repo = open ( ) ? ;
443
let Some ( key) = repo.config.author_key else {
444
println! ( " No identity configured; run `oxy identity create`." ) ;
445
return Ok ( 0 ) ;
446
} ;
447
let id = repo.identity ( & key) ? ;
448
if id.delegates.is_empty ( ) {
449
println! ( " {} " , ui:: dim( " No delegates." ) ) ;
450
}
451
for d in & id.delegates {
75167672 Andy McCall 1 month ago
452
let who = repo
453
.identity ( d)
454
.map ( | i | format! ( " {} <{} >" , i.name, i.email) )
455
.unwrap_or_else ( | _| " (unknown identity)" .into ( ) ) ;
456
println! (
457
" {} {} {} " ,
458
ui:: ok( ui:: glyph( " 🔑" , " *" ) ) ,
459
ui:: hash( d.short ( 12 ) ) ,
460
ui:: author( who)
461
) ;
28fc23d7 Andy McCall 1 month ago ↶
462
}
463
Ok ( 0 )
464
}
465
466
pub fn policy_root_add ( identity_file : & Path, out : Option< PathBuf> ) -> Result< i32 > {
467
let repo = open ( ) ? ;
468
let id = Identity:: decode( & std:: fs:: read( identity_file) ? ) ? ;
469
let key = id.hash ( ) ;
470
let u = repo.policy_root_add ( id.clone ( ) ) ? ;
75167672 Andy McCall 1 month ago
471
println! (
472
" {} root {} <{} > ({} )" ,
473
ui:: done( " Added" ) ,
474
ui:: author( & id.name) ,
475
id.email,
476
key.short ( 12 )
477
) ;
28fc23d7 Andy McCall 1 month ago ↶
478
report_update ( & u, out) ? ;
479
Ok ( 0 )
480
}
481
482
pub fn policy_root_remove ( key : & str , out : Option< PathBuf> ) -> Result< i32 > {
483
let repo = open ( ) ? ;
484
let k = resolve_key ( & repo, key) ? ;
485
let u = repo.policy_root_remove ( & k) ? ;
486
println! ( " {} root {} " , ui:: done( " Removed" ) , ui:: hash( k.short ( 12 ) ) ) ;
487
report_update ( & u, out) ? ;
488
Ok ( 0 )
489
}
490
491
pub fn policy_threshold ( n : u32 , out : Option< PathBuf> ) -> Result< i32 > {
492
let repo = open ( ) ? ;
493
let u = repo.policy_set_threshold ( n) ? ;
494
println! ( " {} root threshold to {n} " , ui:: done( " Set" ) ) ;
495
report_update ( & u, out) ? ;
496
Ok ( 0 )
497
}
498
499
pub fn policy_sign ( proposal : & Path, out : Option< PathBuf> ) -> Result< i32 > {
500
let repo = open ( ) ? ;
501
let sp = SignedPolicy:: decode( & std:: fs:: read( proposal) ? ) ? ;
502
let u = repo.policy_sign ( sp) ? ;
503
println! ( " {} policy serial {} " , ui:: done( " Signed" ) , u.policy.serial) ;
504
report_update ( & u, Some ( out.unwrap_or_else ( | | proposal.to_path_buf ( ) ) ) ) ? ;
505
Ok ( 0 )
506
}
507
83e0677b Andy McCall 1 month ago ↶
508
pub fn policy_init ( ) -> Result< i32 > {
509
let repo = open ( ) ? ;
510
let sp = repo.policy_init ( ) ? ;
75167672 Andy McCall 1 month ago
511
println! (
512
" {} policy with root key {} " ,
513
ui:: done( " Created" ) ,
514
sp.policy.roots[ 0 ] .short ( 12 )
515
) ;
28fc23d7 Andy McCall 1 month ago ↶
516
report_update ( & sp, None ) ? ;
83e0677b Andy McCall 1 month ago ↶
517
Ok ( 0 )
518
}
519
c6aa5bd9 Andy McCall 1 month ago ↶
520
pub fn policy_show ( as_json : bool ) -> Result< i32 > {
83e0677b Andy McCall 1 month ago ↶
521
let repo = open ( ) ? ;
522
let Some ( ( r, sp) ) = repo.current_policy ( ) ? else {
c6aa5bd9 Andy McCall 1 month ago ↶
523
if as_json {
524
println! ( " null" ) ;
525
} else {
75167672 Andy McCall 1 month ago
526
println! (
527
" No policy: this repository is unverified. Run `oxy policy init` to create one."
528
) ;
c6aa5bd9 Andy McCall 1 month ago ↶
529
}
83e0677b Andy McCall 1 month ago ↶
530
return Ok ( 0 ) ;
531
} ;
532
let p = & sp.policy;
c6aa5bd9 Andy McCall 1 month ago ↶
533
if as_json {
534
let ident = | k : & oxy_core:: hash:: KeyId| {
75167672 Andy McCall 1 month ago
535
let ( name, email) = p
536
.identity ( k)
537
.map ( | i | ( i.name.clone ( ) , i.email.clone ( ) ) )
538
.unwrap_or_default ( ) ;
539
json:: obj( [
540
( " key" , json:: s( & k.to_hex ( ) ) ) ,
541
( " name" , json:: s( & name) ) ,
542
( " email" , json:: s( & email) ) ,
543
] )
c6aa5bd9 Andy McCall 1 month ago ↶
544
} ;
545
println! (
546
" {} " ,
547
json:: obj( [
548
( " hash" , json:: s( & r.to_hex ( ) ) ) ,
549
( " serial" , json:: n( p.serial) ) ,
550
( " expires_ms" , json:: n( p.expires) ) ,
75167672 Andy McCall 1 month ago
551
(
552
" expired" ,
553
json:: b( p.is_expired ( oxy_core:: objects:: Hlc:: now( ) .wall_ms) )
554
) ,
c6aa5bd9 Andy McCall 1 month ago ↶
555
( " root_threshold" , json:: n( p.root_threshold) ) ,
556
( " roots" , json:: arr( p.roots.iter ( ) .map ( ident) ) ) ,
557
(
558
" rules" ,
559
json:: arr( p.rules.iter ( ) .map ( | rule | {
560
json:: obj( [
561
( " pattern" , json:: s( & rule.pattern) ) ,
562
( " keys" , json:: arr( rule.keys.iter ( ) .map ( ident) ) ) ,
563
( " threshold" , json:: n( rule.threshold) ) ,
564
( " require" , json:: s( rule.require.name ( ) ) ) ,
565
( " allow_conflicts" , json:: b( rule.allow_conflicts) ) ,
566
] )
567
} ) ) ,
568
) ,
569
( " revoked" , json:: arr( p.revoked.iter ( ) .map ( ident) ) ) ,
570
] )
571
) ;
572
return Ok ( 0 ) ;
573
}
75167672 Andy McCall 1 month ago
574
let name = | k : & oxy_core:: hash:: KeyId| {
575
p.identity ( k)
576
.map ( | i | format! ( " {} <{} >" , i.name, i.email) )
577
.unwrap_or_else ( | | " (unknown identity)" .into ( ) )
578
} ;
94084087 Andy McCall 1 month ago ↶
579
let expired = p.is_expired ( oxy_core:: objects:: Hlc:: now( ) .wall_ms) ;
75167672 Andy McCall 1 month ago
580
println! (
581
" {} {} {} serial {} {} expires {} {} " ,
582
ui:: section( " policy" ) ,
583
ui:: hash( format! ( " #{} " , r.short ( 12 ) ) ) ,
584
ui:: sep( ) ,
585
p.serial,
586
ui:: sep( ) ,
587
when ( p.expires) ,
588
if expired {
589
ui:: err( format! ( " {} EXPIRED" , ui:: glyph( " ✖" , " x" ) ) )
590
} else {
591
String :: new( )
592
}
593
) ;
594
println! (
595
" {} " ,
596
ui:: section( format! (
597
" roots {} " ,
598
ui:: dim( format! ( " (threshold {} )" , p.root_threshold) )
599
) )
600
) ;
83e0677b Andy McCall 1 month ago ↶
601
for k in & p.roots {
75167672 Andy McCall 1 month ago
602
println! (
603
" {} {} {} " ,
604
ui:: ok( ui:: glyph( " 🔑" , " *" ) ) ,
605
ui:: hash( k.short ( 12 ) ) ,
606
ui:: author( name ( k) )
607
) ;
83e0677b Andy McCall 1 month ago ↶
608
}
94084087 Andy McCall 1 month ago ↶
609
println! ( " {} " , ui:: section( " rules" ) ) ;
83e0677b Andy McCall 1 month ago ↶
610
for rule in & p.rules {
75167672 Andy McCall 1 month ago
611
println! (
612
" {} {:12} {} " ,
613
ui:: ok( ui:: glyph( " ▸" , " >" ) ) ,
614
ui:: bookmark( & rule.pattern) ,
615
ui:: dim( format! (
616
" threshold {} , require {} , conflicts {} " ,
617
rule.threshold,
618
rule.require.name ( ) ,
619
if rule.allow_conflicts {
620
" allowed"
621
} else {
622
" refused"
623
}
624
) )
625
) ;
83e0677b Andy McCall 1 month ago ↶
626
for k in & rule.keys {
94084087 Andy McCall 1 month ago ↶
627
println! ( " {} {} " , ui:: hash( k.short ( 12 ) ) , ui:: author( name ( k) ) ) ;
83e0677b Andy McCall 1 month ago ↶
628
}
629
}
630
if ! p.revoked.is_empty ( ) {
94084087 Andy McCall 1 month ago ↶
631
println! ( " {} " , ui:: section( " revoked" ) ) ;
83e0677b Andy McCall 1 month ago ↶
632
for k in & p.revoked {
75167672 Andy McCall 1 month ago
633
println! (
634
" {} {} {} " ,
635
ui:: err( ui:: glyph( " ✖" , " x" ) ) ,
636
ui:: hash( k.short ( 12 ) ) ,
637
ui:: author( name ( k) )
638
) ;
83e0677b Andy McCall 1 month ago ↶
639
}
640
}
641
Ok ( 0 )
642
}
643
75167672 Andy McCall 1 month ago
644
pub fn policy_grant (
645
identity_file : & Path,
646
pattern : Option< & str > ,
647
out : Option< PathBuf> ,
648
) -> Result< i32 > {
83e0677b Andy McCall 1 month ago ↶
649
let repo = open ( ) ? ;
650
let id = Identity:: decode( & std:: fs:: read( identity_file) ? ) ? ;
651
let key = id.hash ( ) ;
28fc23d7 Andy McCall 1 month ago ↶
652
let u = repo.policy_grant ( id.clone ( ) , pattern) ? ;
75167672 Andy McCall 1 month ago
653
println! (
654
" {} {} <{} > ({} ) on {} " ,
655
ui:: done( " Granted" ) ,
656
ui:: author( & id.name) ,
657
id.email,
658
key.short ( 12 ) ,
659
ui:: bookmark( pattern.unwrap_or ( " *" ) )
660
) ;
28fc23d7 Andy McCall 1 month ago ↶
661
report_update ( & u, out) ? ;
83e0677b Andy McCall 1 month ago ↶
662
Ok ( 0 )
663
}
664
28fc23d7 Andy McCall 1 month ago ↶
665
pub fn policy_revoke ( key : & str , out : Option< PathBuf> ) -> Result< i32 > {
83e0677b Andy McCall 1 month ago ↶
666
let repo = open ( ) ? ;
28fc23d7 Andy McCall 1 month ago ↶
667
if repo.current_policy ( ) ? .is_none ( ) {
83e0677b Andy McCall 1 month ago ↶
668
anyhow:: bail! ( " this repository has no policy" ) ;
28fc23d7 Andy McCall 1 month ago ↶
669
}
670
let full = resolve_key ( & repo, key) ? ;
671
let u = repo.policy_revoke ( & full) ? ;
672
println! ( " {} {} " , ui:: done( " Revoked" ) , ui:: hash( full.short ( 12 ) ) ) ;
673
report_update ( & u, out) ? ;
83e0677b Andy McCall 1 month ago ↶
674
Ok ( 0 )
675
}
05e71ed6 Andy McCall 1 month ago ↶
676
677
678
pub fn seal ( specs : & [String], as_json : bool ) -> Result< i32 > {
679
let repo = Repo:: open( & std:: env:: current_dir( ) ? ) ? ;
680
let hashes: Vec< _ > = if specs.is_empty ( ) {
681
repo.sealable ( ) ?
682
} else {
683
specs.iter ( ) .map ( | s | repo.resolve_change ( s) ) .collect:: < std:: result:: Result< _ , _ > > ( ) ?
684
} ;
685
if hashes.is_empty ( ) {
686
if as_json {
687
println! ( " {{ \" sealed\" :[]}} " ) ;
688
} else {
689
println! ( " {} nothing to seal: every change you can authenticate already is" , ui:: done( " Sealed" ) ) ;
690
}
691
return Ok ( 0 ) ;
692
}
693
let mut refs = Vec :: new( ) ;
694
for chunk in hashes.chunks ( oxy_core:: attest:: MAX_SEAL_HASHES ) {
695
refs.push ( repo.seal ( chunk) ? ) ;
696
}
697
if as_json {
698
let list: Vec< String> = hashes.iter ( ) .map ( | h | format! ( " \" {} \" " , h.to_hex ( ) ) ) .collect ( ) ;
699
let seals: Vec< String> = refs.iter ( ) .map ( | r | format! ( " \" {} \" " , r.to_hex ( ) ) ) .collect ( ) ;
700
println! ( " {{ \" sealed\" :[{} ],\" seals\" :[{} ]}} " , list.join ( " ," ) , seals.join ( " ," ) ) ;
701
} else {
702
println! ( " {} {} change{} in {} seal{} ; they travel with your next push" , ui:: done( " Sealed" ) , hashes.len ( ) , if hashes.len ( ) = = 1 { " " } else { " s" } , refs.len ( ) , if refs.len ( ) = = 1 { " " } else { " s" } ) ;
703
for h in & hashes {
704
let title = repo.store ( ) .get_change ( h) .map ( | c | c.message.lines ( ) .next ( ) .unwrap_or ( " " ) .to_string ( ) ) .unwrap_or_default ( ) ;
705
println! ( " {} {} " , ui:: hash( h.short ( 12 ) ) , title) ;
706
}
707
}
708
Ok ( 0 )
709
}