@@ -17,7 +17,6 @@ import (
17
17
"fmt"
18
18
"html/template"
19
19
"io/fs"
20
- "io/ioutil"
21
20
"log"
22
21
"net"
23
22
"net/http"
@@ -35,6 +34,7 @@ import (
35
34
"tailscale.com/client/tailscale"
36
35
"tailscale.com/hostinfo"
37
36
"tailscale.com/ipn"
37
+ "tailscale.com/tailcfg"
38
38
"tailscale.com/tsnet"
39
39
)
40
40
@@ -88,7 +88,7 @@ func Run() error {
88
88
89
89
if * sqlitefile == "" {
90
90
if devMode () {
91
- tmpdir , err := ioutil . TempDir ("" , "golink_dev_*" )
91
+ tmpdir , err := os . MkdirTemp ("" , "golink_dev_*" )
92
92
if err != nil {
93
93
return err
94
94
}
@@ -396,8 +396,8 @@ func serveGo(w http.ResponseWriter, r *http.Request) {
396
396
stats .dirty [link .Short ]++
397
397
stats .mu .Unlock ()
398
398
399
- login , _ := currentUser (r )
400
- env := expandEnv {Now : time .Now ().UTC (), Path : remainder , user : login , query : r .URL .Query ()}
399
+ cu , _ := currentUser (r )
400
+ env := expandEnv {Now : time .Now ().UTC (), Path : remainder , user : cu . login , query : r .URL .Query ()}
401
401
target , err := expandLink (link .Long , env )
402
402
if err != nil {
403
403
log .Printf ("expanding %q: %v" , link .Long , err )
@@ -446,21 +446,24 @@ func serveDetail(w http.ResponseWriter, r *http.Request) {
446
446
return
447
447
}
448
448
449
- login , err := currentUser (r )
449
+ cu , err := currentUser (r )
450
450
if err != nil {
451
451
http .Error (w , err .Error (), http .StatusInternalServerError )
452
452
return
453
453
}
454
+ canEdit := canEditLink (r .Context (), link , cu )
454
455
ownerExists , err := userExists (r .Context (), link .Owner )
455
456
if err != nil {
456
457
log .Printf ("looking up tailnet user %q: %v" , link .Owner , err )
457
458
}
458
459
459
- data := detailData {Link : link }
460
- if link .Owner == login || ! ownerExists {
461
- data .Editable = true
462
- data .Link .Owner = login
463
- data .XSRF = xsrftoken .Generate (xsrfKey , login , short )
460
+ data := detailData {
461
+ Link : link ,
462
+ Editable : canEdit ,
463
+ XSRF : xsrftoken .Generate (xsrfKey , cu .login , short ),
464
+ }
465
+ if canEdit && ! ownerExists {
466
+ data .Link .Owner = cu .login
464
467
}
465
468
466
469
detailTmpl .Execute (w , data )
@@ -541,24 +544,42 @@ func expandLink(long string, env expandEnv) (*url.URL, error) {
541
544
542
545
func devMode () bool { return * dev != "" }
543
546
547
+ const peerCapName = "tailscale.com/golink"
548
+
549
+ type capabilities struct {
550
+ Admin bool `json:"admin"`
551
+ }
552
+
553
+ type user struct {
554
+ login string
555
+ isAdmin bool
556
+ }
557
+
544
558
// currentUser returns the Tailscale user associated with the request.
545
559
// In most cases, this will be the user that owns the device that made the request.
546
560
// For tagged devices, the value "tagged-devices" is returned.
547
561
// If the user can't be determined (such as requests coming through a subnet router),
548
562
// an error is returned unless the -allow-unknown-users flag is set.
549
- var currentUser = func (r * http.Request ) (string , error ) {
563
+ var currentUser = func (r * http.Request ) (user , error ) {
550
564
if devMode () {
551
-
565
+ return user { login : "[email protected] " } ,
nil
552
566
}
553
567
whois , err := localClient .WhoIs (r .Context (), r .RemoteAddr )
554
568
if err != nil {
555
569
if * allowUnknownUsers {
556
570
// Don't report the error if we are allowing unknown users.
557
- return "" , nil
571
+ return user {} , nil
558
572
}
559
- return "" , err
573
+ return user {} , err
560
574
}
561
- return whois .UserProfile .LoginName , nil
575
+ login := whois .UserProfile .LoginName
576
+ caps , _ := tailcfg .UnmarshalCapJSON [capabilities ](whois .CapMap , peerCapName )
577
+ for _ , cap := range caps {
578
+ if cap .Admin {
579
+ return user {login : login , isAdmin : true }, nil
580
+ }
581
+ }
582
+ return user {login : login }, nil
562
583
}
563
584
564
585
// userExists returns whether a user exists with the specified login in the current tailnet.
@@ -597,7 +618,7 @@ func serveDelete(w http.ResponseWriter, r *http.Request) {
597
618
return
598
619
}
599
620
600
- login , err := currentUser (r )
621
+ cu , err := currentUser (r )
601
622
if err != nil {
602
623
http .Error (w , err .Error (), http .StatusInternalServerError )
603
624
return
@@ -609,12 +630,12 @@ func serveDelete(w http.ResponseWriter, r *http.Request) {
609
630
return
610
631
}
611
632
612
- if err := checkLinkOwnership (r .Context (), link , login ); err != nil {
613
- http .Error (w , fmt .Sprintf ("cannot delete link: %v " , err ), http .StatusForbidden )
633
+ if ! canEditLink (r .Context (), link , cu ) {
634
+ http .Error (w , fmt .Sprintf ("cannot delete link owned by %q " , link . Owner ), http .StatusForbidden )
614
635
return
615
636
}
616
637
617
- if ! xsrftoken .Valid (r .PostFormValue ("xsrf" ), xsrfKey , login , short ) {
638
+ if ! xsrftoken .Valid (r .PostFormValue ("xsrf" ), xsrfKey , cu . login , short ) {
618
639
http .Error (w , "invalid XSRF token" , http .StatusBadRequest )
619
640
return
620
641
}
@@ -646,7 +667,7 @@ func serveSave(w http.ResponseWriter, r *http.Request) {
646
667
return
647
668
}
648
669
649
- login , err := currentUser (r )
670
+ cu , err := currentUser (r )
650
671
if err != nil {
651
672
http .Error (w , err .Error (), http .StatusInternalServerError )
652
673
return
@@ -657,8 +678,8 @@ func serveSave(w http.ResponseWriter, r *http.Request) {
657
678
http .Error (w , err .Error (), http .StatusInternalServerError )
658
679
}
659
680
660
- if err := checkLinkOwnership (r .Context (), link , login ); err != nil {
661
- http .Error (w , fmt .Sprintf ("cannot update link: %v " , err ), http .StatusForbidden )
681
+ if ! canEditLink (r .Context (), link , cu ) {
682
+ http .Error (w , fmt .Sprintf ("cannot update link owned by %q " , link . Owner ), http .StatusForbidden )
662
683
return
663
684
}
664
685
@@ -674,7 +695,7 @@ func serveSave(w http.ResponseWriter, r *http.Request) {
674
695
return
675
696
}
676
697
} else {
677
- owner = login
698
+ owner = cu . login
678
699
}
679
700
680
701
now := time .Now ().UTC ()
@@ -701,21 +722,25 @@ func serveSave(w http.ResponseWriter, r *http.Request) {
701
722
}
702
723
}
703
724
704
- func checkLinkOwnership (ctx context.Context , link * Link , login string ) error {
725
+ // canEditLink returns whether the specified user has permission to edit link.
726
+ // Admin users can edit all links.
727
+ // Non-admin users can only edit their own links or links without an active owner.
728
+ func canEditLink (ctx context.Context , link * Link , u user ) bool {
705
729
if link == nil || link .Owner == "" {
706
- return nil
730
+ // new or unowned link
731
+ return true
732
+ }
733
+
734
+ if u .isAdmin || link .Owner == u .login {
735
+ return true
707
736
}
708
737
709
- linkOwnerExists , err := userExists (ctx , link .Owner )
738
+ owned , err := userExists (ctx , link .Owner )
710
739
if err != nil {
711
740
log .Printf ("looking up tailnet user %q: %v" , link .Owner , err )
712
741
}
713
- // Don't allow deleting or updating links if the owner account still exists
714
- // or if we're unsure because an error occurred.
715
- if (linkOwnerExists && link .Owner != login ) || err != nil {
716
- return fmt .Errorf ("link owned by user %q" , link .Owner )
717
- }
718
- return nil
742
+ // Allow editing if the link is currently unowned
743
+ return err == nil && ! owned
719
744
}
720
745
721
746
// serveExport prints a snapshot of the link database. Links are JSON encoded
0 commit comments