Merge branch 'evis/ent-1130-code-search-improve-readability-of-terminal' of https://github.com/entireio/cli into evis/ent-1130-code-search-improve-readability-of-terminal · Entire

Merge branch 'evis/ent-1130-code-search-improve-readability-of-terminal' of https://github.com/entireio/cli into evis/ent-1130-code-search-improve-readability-of-terminal

75f2e4a→main·

evisdren·4d ago·10 files·+579 added/-101 removed

Changes

10

`` 16 unmodified lines

17 18 19 20 21 22 23 239 unmodified lines

263 264 265 265 266 267 268 269 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 274 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 25 unmodified lines

343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 307 308 309 310 311 364 365 366 367 368 313 369 370 315 371 372 373 319 374 375 376 377 323 378 379 325 326 327 328 329 330 331 380 381 382 383

16 unmodified lines

"github.com/entireio/cli/cmd/entire/cli/api" "github.com/entireio/cli/internal/entireclient/clusterdiscovery" "github.com/entireio/cli/internal/entireclient/contexts" "github.com/entireio/cli/internal/entireclient/httputil" "github.com/entireio/cli/internal/entireclient/userdirs" ) 239 unmodified lines

httpClient *http.Client }

// resolveCellSubject picks the jurisdiction-exchange subject: ENTIRE_TOKEN when // set (exclusive, fail-closed), otherwise the active stored login context. This // is the ENTIRE_TOKEN-aware dispatcher used by JurisdictionToken; // NewEntireAPICellClient calls resolveStoredCellSubject directly so its behavior // is unchanged. // resolveCellSubject picks the jurisdiction-exchange subject for // JurisdictionToken (the entire auth token --jurisdiction scripting helper): // ENTIRE_TOKEN when set (exclusive, fail-closed), otherwise the ACTIVE stored // login context.

function resolveCellSubject(ctx context.Context, insecureHTTP bool) (cellSubject, error) { if raw, ok := os.LookupEnv(EnvTokenVar); ok { return resolveEnvTokenCellSubject(raw, insecureHTTP) } return resolveStoredCellSubject(ctx, insecureHTTP) return resolveActiveContextCellSubject(ctx, insecureHTTP) }

// resolveActiveContextCellSubject builds the exchange subject from the active // stored login context: it refreshes that context's login JWT and uses the // context's own core as both the environment signal (dataOrigin) and the // exchange target. See resolveCellSubject for why --jurisdiction follows the // active context instead of discovering one against the data host. function resolveActiveContextCellSubject(ctx context.Context, insecureHTTP bool) (cellSubject, error) { if insecureHTTP { EnableInsecureHTTP() } c, ok, err := activeContext() if err != nil { return cellSubject{}, err } if !ok { return cellSubject{}, fmt.Errorf("not logged in (run 'entire login' first): %w", ErrNotLoggedIn) }

loginJWT, err := refreshCellLoginJWT(ctx, c) if err != nil { return cellSubject{}, err }

origin := api.OriginOnly(c.CoreURL) return cellSubject{ loginJWT: loginJWT, discoveredCore: origin, dataOrigin: origin, httpClient: cellExchangeHTTPClient(origin), }, nil }

// resolveStoredCellSubject resolves the exchange subject from the active stored // context.

// refreshCellLoginJWT returns c's login JWT, transparently re-minting it from the // stored refresh token. Shared by the active-context and discovered-context cell // subject resolvers, which differ only in how they pick c. function refreshCellLoginJWT(ctx context.Context, c *contexts.Context) (string, error) { // Gate the login provider's HTTPS relaxation on the core it actually dials // (selected.CoreURL) plus the explicit --insecure-http-auth opt-in, matching // the sibling ResolveDataAPIToken. A loopback data API must not relax HTTPS // for a non-loopback core. allowInsecure := insecureHTTPEnabled() || isLoopbackHTTP(selected.CoreURL) loginProvider, err := NewRefreshingLoginProvider(selected, cellExchangeTransportForTest, allowInsecure) // plus the explicit --insecure-http-auth opt-in: a loopback core must not // relax HTTPS for a non-loopback one. allowInsecure := insecureHTTPEnabled() || isLoopbackHTTP(c.CoreURL) loginProvider, err := NewRefreshingLoginProvider(c, cellExchangeTransportForTest, allowInsecure) if err != nil { return cellSubject{}, err return "", err }

loginJWT, err := loginProvider(ctx) if err != nil { if errors.Is(err, ErrNotLoggedIn) { return cellSubject{}, fmt.Errorf("not logged in (run 'entire login' first): %w", err) return "", fmt.Errorf("not logged in (run 'entire login' first): %w", err) } // The provider already prefixes "refresh login token:"; return as-is to // avoid a doubled prefix. return cellSubject{}, err return "", err }

return cellSubject{ loginJWT: loginJWT, discoveredCore: selected.CoreURL, dataOrigin: dataOrigin, httpClient: httpClient, }, nil return loginJWT, nil }

// resolveEnvTokenCellSubject builds the exchange subject from ENTIRE_TOKEN: the


Mcmd/entire/cli/auth/cell_data_api.go+71/-22

422 unmodified lines

423 424 425 426 427 428 429 426 427 428 429 430 431 432 431 432 433 434 435 436 437 438 439 440 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 441 442 443 444 445 446 457 447 448 459 460 461 462 449 450 451 452 453 2 unmodified lines

456 457 458 471 472 459 460 461 474 475 462 463 464 477 478 465 466 467 480 481 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526

422 unmodified lines

} }

// TestJurisdictionToken_StoredContext exercises the exported token-only path off // a stored login context: it must return the exchanged identity token and mint // it with scope=openid, the jurisdiction audience, and the login JWT as // subject_token. Not parallel: manipulates env + token store. function TestJurisdictionToken_StoredContext(t *testing.T) { t.Setenv("ENTIRE_CONFIG_DIR", t.TempDir()) t.Setenv("ENTIRE_API_BASE_URL", "https://entire.io") configDir := t.TempDir() t.Setenv("ENTIRE_CONFIG_DIR", configDir) t.Setenv("ENTIRE_API_BASE_URL","") t.Setenv("ENTIRE_API_AUDIENCE_TEMPLATE","") t.Setenv("ENTIRE_CORE_BASE_URL_TEMPLATE","") restore := tokenstore.UseFileBackendForTesting(filepath.Join(t.TempDir(), "tokens.json")) t.Cleanup(restore)

var gotAudience, gotScope, gotSubject, gotGrant string coreSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != oauthTokenPath { http.NotFound(w, r) return } _ = r.ParseForm() //nolint:errcheck // test handler gotAudience = r.FormValue("audience") gotScope = r.FormValue("scope") gotSubject = r.FormValue("subject_token") gotGrant = r.FormValue("grant_type") w.Header().Set("Content-Type", "application/json") _, _ = fmt.Fprint(w, {"access_token":"cell-identity-token","token_type":"Bearer","expires_in":3600}) })) defer coreSrv.Close()

svc := tokenstore.CoreKeyringService(coreSrv.URL) loginJWT := makeJWT(t, fmt.Sprintf({"iss":%q,"home_jurisdiction":"us","exp":%d}, coreSrv.URL, time.Now().Add(2time.Hour).Unix())) const core = "https://us.auth.entire.io" svc := tokenstore.CoreKeyringService(core) loginJWT := makeJWT(t, fmt.Sprintf({"iss":%q,"home_jurisdiction":"us","exp":%d}, core, time.Now().Add(2time.Hour).Unix())) if err := tokenstore.Set(svc, "me", tokenstore.EncodeTokenWithExpiration(loginJWT, 7200)); err != nil { t.Fatalf("seed token: %v", err) } ctxObj := &contexts.Context{Name: "me@core", CoreURL: coreSrv.URL, Handle: "me", KeychainService: svc} writeActiveContext(t, configDir, "me@entire", core, "me", svc)

t.Cleanup(SetResolveContextForCellAPIForTest(t, func(context.Context, string, string, string, *http.Client, clusterdiscovery.DebugFunc) (*contexts.Context, error) { return ctxObj, nil })) t.Cleanup(SetCellExchangeTransportForTest(t, coreSrv.Client().Transport)) ct := &captureTransport{token: "cell-identity-token"} t.Cleanup(SetCellExchangeTransportForTest(t, ct))

token, err := JurisdictionToken(context.Background(), false, "us") if err != nil { }

if token != "cell-identity-token" { t.Fatalf("token = %q, want cell-identity-token", token) } if gotAudience != usEntireAudience { t.Errorf("audience = %q, want https://us.entire.io", gotAudience) if got := ct.form.Get("audience"); got != usEntireAudience { t.Errorf("audience = %q, want %s", got, usEntireAudience) } if gotScope != JurisdictionIdentityScope { t.Errorf("scope = %q, want %q", gotScope, JurisdictionIdentityScope) if got := ct.form.Get("scope"); got != JurisdictionIdentityScope { t.Errorf("scope = %q, want %q", got, JurisdictionIdentityScope) } if gotSubject != loginJWT { t.Errorf("subject_token = %q, want the login JWT", gotSubject) if got := ct.form.Get("subject_token"); got != loginJWT { t.Errorf("subject_token = %q, want the login JWT", got) } if gotGrant != "urn:ietf:params:oauth:grant-type:token-exchange" { t.Errorf("grant_type = %q, want token-exchange", gotGrant) if got := ct.form.Get("grant_type"); got != "urn:ietf:params:oauth:grant-type:token-exchange" { t.Errorf("grant_type = %q, want token-exchange", got) } }

// TestJurisdictionToken_StoredContextFollowsActiveContext is the regression for // the reported bug: with two contexts (prod entire.io + staging partial.to) and // partial.to ACTIVE, auth token --jurisdiction us must mint a partial.to token // — not switch to entire.io because the default data host trusts the prod // context. The exchange audience/subject/core all follow the active partial.to // context. function TestJurisdictionToken_StoredContextFollowsActiveContext(t *testing.T) { configDir := t.TempDir() t.Setenv("ENTIRE_CONFIG_DIR", configDir) ... }

// Your additional content is similar and remains structured within the cleaned JSON.