github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/resolve.go (about) 1 package teams 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "golang.org/x/sync/errgroup" 9 10 "github.com/keybase/client/go/engine" 11 "github.com/keybase/client/go/externals" 12 "github.com/keybase/client/go/libkb" 13 keybase1 "github.com/keybase/client/go/protocol/keybase1" 14 ) 15 16 // ResolveIDToName takes a team ID and resolves it to a name. 17 // It can use server-assist but always cryptographically checks the result. 18 func ResolveIDToName(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID) (name keybase1.TeamName, err error) { 19 return resolveIDToName(ctx, g, id, false) 20 } 21 22 // ResolveIDToNameForceRefresh is like ResolveIDToName but forces a refresh of 23 // the FTL cache. 24 func ResolveIDToNameForceRefresh(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID) (name keybase1.TeamName, err error) { 25 return resolveIDToName(ctx, g, id, true) 26 } 27 28 func resolveIDToName(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID, forceRefresh bool) (name keybase1.TeamName, err error) { 29 m := libkb.NewMetaContext(ctx, g) 30 rres := g.Resolver.ResolveFullExpression(m, fmt.Sprintf("tid:%s", id)) 31 if err = rres.GetError(); err != nil { 32 return keybase1.TeamName{}, err 33 } 34 name = rres.GetTeamName() 35 if err = g.GetFastTeamLoader().VerifyTeamName(m, id, name, forceRefresh); err != nil { 36 return keybase1.TeamName{}, err 37 } 38 39 return name, nil 40 } 41 42 // ResolveNameToID takes a team name and resolves it to a team ID. 43 // It can use server-assist but always cryptographically checks the result. 44 func ResolveNameToID(ctx context.Context, g *libkb.GlobalContext, name keybase1.TeamName) (id keybase1.TeamID, err error) { 45 return resolveNameToID(ctx, g, name, false) 46 } 47 48 // ResolveNameToIDForceRefresh is just like ResolveNameToID but it forces a refresh. 49 func ResolveNameToIDForceRefresh(ctx context.Context, g *libkb.GlobalContext, name keybase1.TeamName) (id keybase1.TeamID, err error) { 50 return resolveNameToID(ctx, g, name, true) 51 } 52 53 func resolveNameToID(ctx context.Context, g *libkb.GlobalContext, name keybase1.TeamName, forceRefresh bool) (id keybase1.TeamID, err error) { 54 m := libkb.NewMetaContext(ctx, g) 55 rres := g.Resolver.ResolveFullExpression(m, fmt.Sprintf("team:%s", name)) 56 if err = rres.GetError(); err != nil { 57 return keybase1.TeamID(""), err 58 } 59 id = rres.GetTeamID() 60 if err = g.GetFastTeamLoader().VerifyTeamName(m, id, name, forceRefresh); err != nil { 61 return keybase1.TeamID(""), err 62 } 63 return id, nil 64 } 65 66 func PurgeResolverTeamID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID) error { 67 m := libkb.NewMetaContext(ctx, g) 68 return g.Resolver.PurgeResolveCache(m, fmt.Sprintf("tid:%s", teamID)) 69 } 70 71 // Resolve assertions in an implicit team display name and verify the result. 72 // Resolve an implicit team name with assertions like "alice,bob+bob@twitter#char (conflicted copy 2017-03-04 #1)" 73 // Into "alice,bob#char (conflicted copy 2017-03-04 #1)" 74 // The input can contain compound assertions, but if compound assertions are left unresolved, an error will be returned. 75 func ResolveImplicitTeamDisplayName(ctx context.Context, g *libkb.GlobalContext, 76 name string, public bool) (res keybase1.ImplicitTeamDisplayName, err error) { 77 78 defer g.CTrace(ctx, fmt.Sprintf("ResolveImplicitTeamDisplayName(%v, public:%v)", name, public), &err)() 79 80 split1 := strings.SplitN(name, " ", 2) // split1: [assertions, ?conflict] 81 assertions := split1[0] 82 var suffix string 83 if len(split1) > 1 { 84 suffix = split1[1] 85 } 86 87 writerAssertions, readerAssertions, err := externals.ParseAssertionsWithReaders(libkb.NewMetaContext(ctx, g), assertions) 88 if err != nil { 89 return res, err 90 } 91 92 res = keybase1.ImplicitTeamDisplayName{ 93 IsPublic: public, 94 } 95 if len(suffix) > 0 { 96 res.ConflictInfo, err = libkb.ParseImplicitTeamDisplayNameSuffix(suffix) 97 if err != nil { 98 return res, err 99 } 100 } 101 102 var resolvedAssertions []libkb.ResolvedAssertion 103 if err = ResolveImplicitTeamSetUntrusted(ctx, g, writerAssertions, &res.Writers, &resolvedAssertions); err != nil { 104 return res, err 105 } 106 if err = ResolveImplicitTeamSetUntrusted(ctx, g, readerAssertions, &res.Readers, &resolvedAssertions); err != nil { 107 return res, err 108 } 109 110 deduplicateImplicitTeamDisplayName(&res) 111 112 // errgroup collects errors and returns the first non-nil. 113 // subctx is canceled when the group finishes. 114 group, subctx := errgroup.WithContext(ctx) 115 116 // Identify everyone who resolved in parallel, checking that they match their resolved UID and original assertions. 117 for _, resolvedAssertion := range resolvedAssertions { 118 resolvedAssertion := resolvedAssertion // https://golang.org/doc/faq#closures_and_goroutines 119 group.Go(func() error { 120 return verifyResolveResult(subctx, g, resolvedAssertion) 121 }) 122 } 123 124 err = group.Wait() 125 return res, err 126 } 127 128 // preventTeamCreationOnError checks if an error coming from resolver should 129 // prevent us from creating a team. We don't want a team where we don't know if 130 // SBS user is resolvable but we just were unable to get the answer. 131 func shouldPreventTeamCreation(err error) bool { 132 if resErr, ok := err.(libkb.ResolutionError); ok { 133 switch resErr.Kind { 134 case libkb.ResolutionErrorRateLimited, libkb.ResolutionErrorInvalidInput, libkb.ResolutionErrorRequestFailed: 135 return true 136 } 137 } 138 return false 139 } 140 141 // Try to resolve implicit team members. 142 // Modifies the arguments `resSet` and appends to `resolvedAssertions`. 143 // For each assertion in `sourceAssertions`, try to resolve them. 144 // 145 // If they resolve, add the username to `resSet` and the assertion to `resolvedAssertions`. 146 // If they don't resolve, add the SocialAssertion to `resSet`, but nothing to `resolvedAssertions`. 147 func ResolveImplicitTeamSetUntrusted(ctx context.Context, g *libkb.GlobalContext, 148 sourceAssertions []libkb.AssertionExpression, resSet *keybase1.ImplicitTeamUserSet, resolvedAssertions *[]libkb.ResolvedAssertion) error { 149 150 m := libkb.NewMetaContext(ctx, g) 151 152 for _, expr := range sourceAssertions { 153 u, resolveRes, err := g.Resolver.ResolveUser(m, expr.String()) 154 if err != nil { 155 // Resolution failed. Could still be an SBS assertion. 156 if shouldPreventTeamCreation(err) { 157 // but if we are not sure, better to bail out 158 return err 159 } 160 sa, err := expr.ToSocialAssertion() 161 if err != nil { 162 // Could not convert to a social assertion. 163 // This could be because it is a compound assertion, which we do not support when SBS. 164 // Or it could be because it's a team assertion or something weird like that. 165 return libkb.ResolutionError{Input: expr.String(), Msg: "unknown user assertion", 166 Kind: libkb.ResolutionErrorNotFound} 167 } 168 resSet.UnresolvedUsers = append(resSet.UnresolvedUsers, sa) 169 } else { 170 // Resolution succeeded 171 resSet.KeybaseUsers = append(resSet.KeybaseUsers, u.Username) 172 // Append the resolvee and assertion to resolvedAssertions, in case we identify later. 173 *resolvedAssertions = append(*resolvedAssertions, libkb.ResolvedAssertion{ 174 UID: u.Uid, 175 Assertion: expr, 176 ResolveResult: resolveRes, 177 }) 178 } 179 } 180 return nil 181 } 182 183 // Verify using Identify that a UID matches an assertion. 184 func verifyResolveResult(ctx context.Context, g *libkb.GlobalContext, resolvedAssertion libkb.ResolvedAssertion) (err error) { 185 186 defer g.CTrace(ctx, fmt.Sprintf("verifyResolveResult ID user [%s] %s", resolvedAssertion.UID, resolvedAssertion.Assertion.String()), 187 &err)() 188 189 if resolvedAssertion.ResolveResult.WasKBAssertion() { 190 // The resolver does not use server-trust for these sorts of assertions. 191 // So early out to avoid the performance cost of a full identify. 192 return nil 193 } 194 195 if resolvedAssertion.ResolveResult.IsServerTrust() { 196 g.Log.CDebugf(ctx, "Trusting the server on assertion: %q (server trust - no way for clients to verify)", resolvedAssertion.Assertion.String()) 197 return nil 198 } 199 200 id2arg := keybase1.Identify2Arg{ 201 Uid: resolvedAssertion.UID, 202 UserAssertion: resolvedAssertion.Assertion.String(), 203 CanSuppressUI: true, 204 // Use CHAT_GUI to avoid tracker popups and DO externals checks. 205 IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_GUI, 206 } 207 208 uis := libkb.UIs{ 209 // Send a nil IdentifyUI, this IdentifyBehavior should not use it anyway. 210 IdentifyUI: nil, 211 } 212 213 eng := engine.NewIdentify2WithUID(g, &id2arg) 214 m := libkb.NewMetaContext(ctx, g).WithUIs(uis) 215 err = engine.RunEngine2(m, eng) 216 if err != nil { 217 idRes, _ := eng.Result(m) 218 m.Debug("identify failed (IDres %v, TrackBreaks %v): %v", idRes != nil, idRes != nil && idRes.TrackBreaks != nil, err) 219 } 220 return err 221 } 222 223 // Remove duplicates from a team display name. 224 // Does not do any resolution nor resolution of UIDs. 225 // "alice" -> "alice" 226 // "alice,bob,alice" -> "alice" 227 // "alice#alice" -> "alice" 228 // "alice,bob#alice#char" -> "alice,bob#char" 229 func deduplicateImplicitTeamDisplayName(name *keybase1.ImplicitTeamDisplayName) { 230 seen := make(map[string]bool) 231 232 unseen := func(idx string) bool { 233 seenBefore := seen[idx] 234 seen[idx] = true 235 return !seenBefore 236 } 237 238 var writers keybase1.ImplicitTeamUserSet 239 var readers keybase1.ImplicitTeamUserSet 240 241 for _, u := range name.Writers.KeybaseUsers { 242 if unseen(u) { 243 writers.KeybaseUsers = append(writers.KeybaseUsers, u) 244 } 245 } 246 for _, u := range name.Writers.UnresolvedUsers { 247 if unseen(u.String()) { 248 writers.UnresolvedUsers = append(writers.UnresolvedUsers, u) 249 } 250 } 251 252 for _, u := range name.Readers.KeybaseUsers { 253 if unseen(u) { 254 readers.KeybaseUsers = append(readers.KeybaseUsers, u) 255 } 256 } 257 for _, u := range name.Readers.UnresolvedUsers { 258 if unseen(u.String()) { 259 readers.UnresolvedUsers = append(readers.UnresolvedUsers, u) 260 } 261 } 262 263 name.Writers = writers 264 name.Readers = readers 265 }