| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/ui/login/login_handler.h" | 5 #include "chrome/browser/ui/login/login_handler.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 #include <vector> | 8 #include <vector> |
| 9 | 9 |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| (...skipping 14 matching lines...) Expand all Loading... |
| 25 #include "components/url_formatter/elide_url.h" | 25 #include "components/url_formatter/elide_url.h" |
| 26 #include "content/public/browser/browser_thread.h" | 26 #include "content/public/browser/browser_thread.h" |
| 27 #include "content/public/browser/notification_registrar.h" | 27 #include "content/public/browser/notification_registrar.h" |
| 28 #include "content/public/browser/notification_service.h" | 28 #include "content/public/browser/notification_service.h" |
| 29 #include "content/public/browser/render_frame_host.h" | 29 #include "content/public/browser/render_frame_host.h" |
| 30 #include "content/public/browser/resource_dispatcher_host.h" | 30 #include "content/public/browser/resource_dispatcher_host.h" |
| 31 #include "content/public/browser/resource_request_info.h" | 31 #include "content/public/browser/resource_request_info.h" |
| 32 #include "content/public/browser/web_contents.h" | 32 #include "content/public/browser/web_contents.h" |
| 33 #include "content/public/common/origin_util.h" | 33 #include "content/public/common/origin_util.h" |
| 34 #include "net/base/auth.h" | 34 #include "net/base/auth.h" |
| 35 #include "net/base/host_port_pair.h" |
| 35 #include "net/base/load_flags.h" | 36 #include "net/base/load_flags.h" |
| 36 #include "net/http/http_auth_scheme.h" | 37 #include "net/http/http_auth_scheme.h" |
| 37 #include "net/http/http_transaction_factory.h" | 38 #include "net/http/http_transaction_factory.h" |
| 38 #include "net/url_request/url_request.h" | 39 #include "net/url_request/url_request.h" |
| 39 #include "net/url_request/url_request_context.h" | 40 #include "net/url_request/url_request_context.h" |
| 40 #include "ui/base/l10n/l10n_util.h" | 41 #include "ui/base/l10n/l10n_util.h" |
| 41 #include "ui/gfx/text_elider.h" | 42 #include "ui/gfx/text_elider.h" |
| 42 | 43 |
| 43 #if defined(ENABLE_EXTENSIONS) | 44 #if defined(ENABLE_EXTENSIONS) |
| 44 #include "components/guest_view/browser/guest_view_base.h" | 45 #include "components/guest_view/browser/guest_view_base.h" |
| (...skipping 13 matching lines...) Expand all Loading... |
| 58 | 59 |
| 59 namespace { | 60 namespace { |
| 60 | 61 |
| 61 // Helper to remove the ref from an net::URLRequest to the LoginHandler. | 62 // Helper to remove the ref from an net::URLRequest to the LoginHandler. |
| 62 // Should only be called from the IO thread, since it accesses an | 63 // Should only be called from the IO thread, since it accesses an |
| 63 // net::URLRequest. | 64 // net::URLRequest. |
| 64 void ResetLoginHandlerForRequest(net::URLRequest* request) { | 65 void ResetLoginHandlerForRequest(net::URLRequest* request) { |
| 65 ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request); | 66 ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request); |
| 66 } | 67 } |
| 67 | 68 |
| 68 // Helper to create a PasswordForm for PasswordManager to start looking for | |
| 69 // saved credentials. | |
| 70 PasswordForm MakeInputForPasswordManager(const GURL& request_url, | |
| 71 net::AuthChallengeInfo* auth_info) { | |
| 72 PasswordForm dialog_form; | |
| 73 if (base::LowerCaseEqualsASCII(auth_info->scheme, net::kBasicAuthScheme)) { | |
| 74 dialog_form.scheme = PasswordForm::SCHEME_BASIC; | |
| 75 } else if (base::LowerCaseEqualsASCII(auth_info->scheme, | |
| 76 net::kDigestAuthScheme)) { | |
| 77 dialog_form.scheme = PasswordForm::SCHEME_DIGEST; | |
| 78 } else { | |
| 79 dialog_form.scheme = PasswordForm::SCHEME_OTHER; | |
| 80 } | |
| 81 std::string host_and_port(auth_info->challenger.ToString()); | |
| 82 if (auth_info->is_proxy) { | |
| 83 std::string origin = host_and_port; | |
| 84 // We don't expect this to already start with http:// or https://. | |
| 85 DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0); | |
| 86 origin = std::string("http://") + origin; | |
| 87 dialog_form.origin = GURL(origin); | |
| 88 } else if (!auth_info->challenger.Equals( | |
| 89 net::HostPortPair::FromURL(request_url))) { | |
| 90 dialog_form.origin = GURL(); | |
| 91 NOTREACHED(); // crbug.com/32718 | |
| 92 } else { | |
| 93 dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port); | |
| 94 } | |
| 95 dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info); | |
| 96 return dialog_form; | |
| 97 } | |
| 98 | |
| 99 void ShowLoginPrompt(const GURL& request_url, | |
| 100 net::AuthChallengeInfo* auth_info, | |
| 101 LoginHandler* handler) { | |
| 102 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 103 WebContents* parent_contents = handler->GetWebContentsForLogin(); | |
| 104 if (!parent_contents) | |
| 105 return; | |
| 106 prerender::PrerenderContents* prerender_contents = | |
| 107 prerender::PrerenderContents::FromWebContents(parent_contents); | |
| 108 if (prerender_contents) { | |
| 109 prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED); | |
| 110 return; | |
| 111 } | |
| 112 | |
| 113 base::string16 authority = l10n_util::GetStringFUTF16( | |
| 114 auth_info->is_proxy ? IDS_LOGIN_DIALOG_PROXY_AUTHORITY | |
| 115 : IDS_LOGIN_DIALOG_AUTHORITY, | |
| 116 url_formatter::FormatUrlForSecurityDisplay(request_url)); | |
| 117 base::string16 explanation; | |
| 118 if (!content::IsOriginSecure(request_url)) { | |
| 119 explanation = | |
| 120 l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_NON_SECURE_TRANSPORT); | |
| 121 } | |
| 122 | |
| 123 password_manager::PasswordManager* password_manager = | |
| 124 handler->GetPasswordManagerForLogin(); | |
| 125 | |
| 126 if (!password_manager) { | |
| 127 #if defined(ENABLE_EXTENSIONS) | |
| 128 // A WebContents in a <webview> (a GuestView type) does not have a password | |
| 129 // manager, but still needs to be able to show login prompts. | |
| 130 const auto* guest = | |
| 131 guest_view::GuestViewBase::FromWebContents(parent_contents); | |
| 132 if (guest && | |
| 133 extensions::GetViewType(guest->owner_web_contents()) != | |
| 134 extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) { | |
| 135 handler->BuildViewWithoutPasswordManager(authority, explanation); | |
| 136 return; | |
| 137 } | |
| 138 #endif | |
| 139 handler->CancelAuth(); | |
| 140 return; | |
| 141 } | |
| 142 | |
| 143 if (password_manager && | |
| 144 password_manager->client()->GetLogManager()->IsLoggingActive()) { | |
| 145 password_manager::BrowserSavePasswordProgressLogger logger( | |
| 146 password_manager->client()->GetLogManager()); | |
| 147 logger.LogMessage( | |
| 148 autofill::SavePasswordProgressLogger::STRING_SHOW_LOGIN_PROMPT_METHOD); | |
| 149 } | |
| 150 | |
| 151 PasswordForm observed_form( | |
| 152 MakeInputForPasswordManager(request_url, auth_info)); | |
| 153 handler->BuildViewWithPasswordManager(authority, explanation, | |
| 154 password_manager, observed_form); | |
| 155 } | |
| 156 | |
| 157 } // namespace | 69 } // namespace |
| 158 | 70 |
| 159 // ---------------------------------------------------------------------------- | 71 // ---------------------------------------------------------------------------- |
| 160 // LoginHandler | 72 // LoginHandler |
| 161 | 73 |
| 162 LoginHandler::LoginModelData::LoginModelData( | 74 LoginHandler::LoginModelData::LoginModelData( |
| 163 password_manager::LoginModel* login_model, | 75 password_manager::LoginModel* login_model, |
| 164 const autofill::PasswordForm& observed_form) | 76 const autofill::PasswordForm& observed_form) |
| 165 : model(login_model), form(observed_form) { | 77 : model(login_model), form(observed_form) { |
| 166 DCHECK(model); | 78 DCHECK(model); |
| (...skipping 338 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 505 } | 417 } |
| 506 | 418 |
| 507 // Closes the view_contents from the UI loop. | 419 // Closes the view_contents from the UI loop. |
| 508 void LoginHandler::CloseContentsDeferred() { | 420 void LoginHandler::CloseContentsDeferred() { |
| 509 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 421 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 510 CloseDialog(); | 422 CloseDialog(); |
| 511 if (interstitial_delegate_) | 423 if (interstitial_delegate_) |
| 512 interstitial_delegate_->Proceed(); | 424 interstitial_delegate_->Proceed(); |
| 513 } | 425 } |
| 514 | 426 |
| 515 // This callback is run on the UI thread and creates a constrained window with | 427 // static |
| 516 // a LoginView to prompt the user. If the prompt is triggered because of | 428 std::string LoginHandler::GetSignonRealm( |
| 517 // a cross origin navigation in the main frame, a blank interstitial is first | 429 const GURL& url, |
| 518 // created which in turn creates the LoginView. Otherwise, a LoginView is | 430 const net::AuthChallengeInfo& auth_info) { |
| 519 // directly in this callback. In both cases, the response will be sent to | 431 std::string signon_realm; |
| 520 // LoginHandler, which then routes it to the net::URLRequest on the I/O thread. | 432 if (auth_info.is_proxy) { |
| 521 void LoginDialogCallback(const GURL& request_url, | 433 // Historically we've been storing the signon realm for proxies using |
| 522 net::AuthChallengeInfo* auth_info, | 434 // net::HostPortPair::ToString(). |
| 523 LoginHandler* handler, | 435 net::HostPortPair host_port_pair = |
| 524 bool is_main_frame) { | 436 net::HostPortPair::FromURL(GURL(auth_info.challenger.Serialize())); |
| 437 signon_realm = host_port_pair.ToString(); |
| 438 signon_realm.append("/"); |
| 439 } else { |
| 440 // Take scheme, host, and port from the url. |
| 441 signon_realm = url.GetOrigin().spec(); |
| 442 // This ends with a "/". |
| 443 } |
| 444 signon_realm.append(auth_info.realm); |
| 445 return signon_realm; |
| 446 } |
| 447 |
| 448 // static |
| 449 PasswordForm LoginHandler::MakeInputForPasswordManager( |
| 450 const GURL& request_url, |
| 451 const net::AuthChallengeInfo& auth_info) { |
| 452 PasswordForm dialog_form; |
| 453 if (base::LowerCaseEqualsASCII(auth_info.scheme, net::kBasicAuthScheme)) { |
| 454 dialog_form.scheme = PasswordForm::SCHEME_BASIC; |
| 455 } else if (base::LowerCaseEqualsASCII(auth_info.scheme, |
| 456 net::kDigestAuthScheme)) { |
| 457 dialog_form.scheme = PasswordForm::SCHEME_DIGEST; |
| 458 } else { |
| 459 dialog_form.scheme = PasswordForm::SCHEME_OTHER; |
| 460 } |
| 461 if (auth_info.is_proxy) { |
| 462 dialog_form.origin = GURL(auth_info.challenger.Serialize()); |
| 463 } else if (!auth_info.challenger.IsSameOriginWith(url::Origin(request_url))) { |
| 464 dialog_form.origin = GURL(); |
| 465 NOTREACHED(); // crbug.com/32718 |
| 466 } else { |
| 467 dialog_form.origin = GURL(auth_info.challenger.Serialize()); |
| 468 } |
| 469 dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, auth_info); |
| 470 return dialog_form; |
| 471 } |
| 472 |
| 473 // static |
| 474 void LoginHandler::GetDialogStrings(const GURL& request_url, |
| 475 const net::AuthChallengeInfo& auth_info, |
| 476 base::string16* authority, |
| 477 base::string16* explanation) { |
| 478 GURL authority_url; |
| 479 |
| 480 if (auth_info.is_proxy) { |
| 481 *authority = l10n_util::GetStringFUTF16( |
| 482 IDS_LOGIN_DIALOG_PROXY_AUTHORITY, |
| 483 url_formatter::FormatOriginForSecurityDisplay( |
| 484 auth_info.challenger, url_formatter::SchemeDisplay::SHOW)); |
| 485 authority_url = GURL(auth_info.challenger.Serialize()); |
| 486 } else { |
| 487 *authority = l10n_util::GetStringFUTF16( |
| 488 IDS_LOGIN_DIALOG_AUTHORITY, |
| 489 url_formatter::FormatUrlForSecurityDisplay(request_url)); |
| 490 authority_url = request_url; |
| 491 } |
| 492 |
| 493 if (!content::IsOriginSecure(authority_url)) { |
| 494 // TODO(asanka): The string should be different for proxies and servers. |
| 495 // http://crbug.com/620756 |
| 496 *explanation = |
| 497 l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_NON_SECURE_TRANSPORT); |
| 498 } else { |
| 499 explanation->clear(); |
| 500 } |
| 501 } |
| 502 |
| 503 // static |
| 504 void LoginHandler::ShowLoginPrompt(const GURL& request_url, |
| 505 net::AuthChallengeInfo* auth_info, |
| 506 LoginHandler* handler) { |
| 507 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 508 WebContents* parent_contents = handler->GetWebContentsForLogin(); |
| 509 if (!parent_contents) |
| 510 return; |
| 511 prerender::PrerenderContents* prerender_contents = |
| 512 prerender::PrerenderContents::FromWebContents(parent_contents); |
| 513 if (prerender_contents) { |
| 514 prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED); |
| 515 return; |
| 516 } |
| 517 |
| 518 base::string16 authority; |
| 519 base::string16 explanation; |
| 520 GetDialogStrings(request_url, *auth_info, &authority, &explanation); |
| 521 |
| 522 password_manager::PasswordManager* password_manager = |
| 523 handler->GetPasswordManagerForLogin(); |
| 524 |
| 525 if (!password_manager) { |
| 526 #if defined(ENABLE_EXTENSIONS) |
| 527 // A WebContents in a <webview> (a GuestView type) does not have a password |
| 528 // manager, but still needs to be able to show login prompts. |
| 529 const auto* guest = |
| 530 guest_view::GuestViewBase::FromWebContents(parent_contents); |
| 531 if (guest && |
| 532 extensions::GetViewType(guest->owner_web_contents()) != |
| 533 extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) { |
| 534 handler->BuildViewWithoutPasswordManager(authority, explanation); |
| 535 return; |
| 536 } |
| 537 #endif |
| 538 handler->CancelAuth(); |
| 539 return; |
| 540 } |
| 541 |
| 542 if (password_manager && |
| 543 password_manager->client()->GetLogManager()->IsLoggingActive()) { |
| 544 password_manager::BrowserSavePasswordProgressLogger logger( |
| 545 password_manager->client()->GetLogManager()); |
| 546 logger.LogMessage( |
| 547 autofill::SavePasswordProgressLogger::STRING_SHOW_LOGIN_PROMPT_METHOD); |
| 548 } |
| 549 |
| 550 PasswordForm observed_form( |
| 551 LoginHandler::MakeInputForPasswordManager(request_url, *auth_info)); |
| 552 handler->BuildViewWithPasswordManager(authority, explanation, |
| 553 password_manager, observed_form); |
| 554 } |
| 555 |
| 556 // static |
| 557 void LoginHandler::LoginDialogCallback(const GURL& request_url, |
| 558 net::AuthChallengeInfo* auth_info, |
| 559 LoginHandler* handler, |
| 560 bool is_main_frame) { |
| 525 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 561 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 526 WebContents* parent_contents = handler->GetWebContentsForLogin(); | 562 WebContents* parent_contents = handler->GetWebContentsForLogin(); |
| 527 if (!parent_contents || handler->WasAuthHandled()) { | 563 if (!parent_contents || handler->WasAuthHandled()) { |
| 528 // The request may have been canceled, or it may be for a renderer not | 564 // The request may have been canceled, or it may be for a renderer not |
| 529 // hosted by a tab (e.g. an extension). Cancel just in case (canceling twice | 565 // hosted by a tab (e.g. an extension). Cancel just in case (canceling twice |
| 530 // is a no-op). | 566 // is a no-op). |
| 531 handler->CancelAuth(); | 567 handler->CancelAuth(); |
| 532 return; | 568 return; |
| 533 } | 569 } |
| 534 | 570 |
| 535 // Check if this is a main frame navigation and | 571 // Check if this is a main frame navigation and |
| 536 // (a) if the request is cross origin or | 572 // (a) if the request is cross origin or |
| 537 // (b) if an interstitial is already being shown. | 573 // (b) if an interstitial is already being shown or |
| 574 // (c) the prompt is for proxy authentication |
| 538 // | 575 // |
| 539 // For (a), there are two different ways the navigation can occur: | 576 // For (a), there are two different ways the navigation can occur: |
| 540 // 1- The user enters the resource URL in the omnibox. | 577 // 1- The user enters the resource URL in the omnibox. |
| 541 // 2- The page redirects to the resource. | 578 // 2- The page redirects to the resource. |
| 542 // In both cases, the last committed URL is different than the resource URL, | 579 // In both cases, the last committed URL is different than the resource URL, |
| 543 // so checking it is sufficient. | 580 // so checking it is sufficient. |
| 544 // Note that (1) will not be true once site isolation is enabled, as any | 581 // Note that (1) will not be true once site isolation is enabled, as any |
| 545 // navigation could cause a cross-process swap, including link clicks. | 582 // navigation could cause a cross-process swap, including link clicks. |
| 546 // | 583 // |
| 547 // For (b), the login interstitial should always replace an existing | 584 // For (b), the login interstitial should always replace an existing |
| 548 // interstitial. This is because |LoginHandler::CloseContentsDeferred| tries | 585 // interstitial. This is because |LoginHandler::CloseContentsDeferred| tries |
| 549 // to proceed whatever interstitial is being shown when the login dialog is | 586 // to proceed whatever interstitial is being shown when the login dialog is |
| 550 // closed, so that interstitial should only be a login interstitial. | 587 // closed, so that interstitial should only be a login interstitial. |
| 551 if (is_main_frame && (parent_contents->ShowingInterstitialPage() || | 588 // |
| 552 parent_contents->GetLastCommittedURL().GetOrigin() != | 589 // For (c), the authority information in the omnibox will be (and should be) |
| 553 request_url.GetOrigin())) { | 590 // different from the authority information in the authentication prompt. An |
| 591 // interstitial with an empty URL clears the omnibox and reduces the possible |
| 592 // user confusion that may result from the different authority information |
| 593 // being displayed simultaneously. This is specially important when the proxy |
| 594 // is accessed via an open connection while the target server is considered |
| 595 // secure. |
| 596 if (is_main_frame && |
| 597 (parent_contents->ShowingInterstitialPage() || auth_info->is_proxy || |
| 598 parent_contents->GetLastCommittedURL().GetOrigin() != |
| 599 request_url.GetOrigin())) { |
| 554 // Show a blank interstitial for main-frame, cross origin requests | 600 // Show a blank interstitial for main-frame, cross origin requests |
| 555 // so that the correct URL is shown in the omnibox. | 601 // so that the correct URL is shown in the omnibox. |
| 556 base::Closure callback = | 602 base::Closure callback = |
| 557 base::Bind(&ShowLoginPrompt, request_url, base::RetainedRef(auth_info), | 603 base::Bind(&LoginHandler::ShowLoginPrompt, request_url, |
| 558 base::RetainedRef(handler)); | 604 base::RetainedRef(auth_info), base::RetainedRef(handler)); |
| 559 // The interstitial delegate is owned by the interstitial that it creates. | 605 // The interstitial delegate is owned by the interstitial that it creates. |
| 560 // This cancels any existing interstitial. | 606 // This cancels any existing interstitial. |
| 561 handler->SetInterstitialDelegate( | 607 handler->SetInterstitialDelegate( |
| 562 (new LoginInterstitialDelegate(parent_contents, request_url, callback)) | 608 (new LoginInterstitialDelegate( |
| 609 parent_contents, auth_info->is_proxy ? GURL() : request_url, |
| 610 callback)) |
| 563 ->GetWeakPtr()); | 611 ->GetWeakPtr()); |
| 564 } else { | 612 } else { |
| 565 ShowLoginPrompt(request_url, auth_info, handler); | 613 ShowLoginPrompt(request_url, auth_info, handler); |
| 566 } | 614 } |
| 567 } | 615 } |
| 568 | 616 |
| 569 // ---------------------------------------------------------------------------- | 617 // ---------------------------------------------------------------------------- |
| 570 // Public API | 618 // Public API |
| 571 | 619 |
| 572 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info, | 620 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info, |
| 573 net::URLRequest* request) { | 621 net::URLRequest* request) { |
| 574 bool is_main_frame = (request->load_flags() & net::LOAD_MAIN_FRAME) != 0; | 622 bool is_main_frame = (request->load_flags() & net::LOAD_MAIN_FRAME) != 0; |
| 575 LoginHandler* handler = LoginHandler::Create(auth_info, request); | 623 LoginHandler* handler = LoginHandler::Create(auth_info, request); |
| 576 BrowserThread::PostTask( | 624 BrowserThread::PostTask( |
| 577 BrowserThread::UI, FROM_HERE, | 625 BrowserThread::UI, FROM_HERE, |
| 578 base::Bind(&LoginDialogCallback, request->url(), | 626 base::Bind(&LoginHandler::LoginDialogCallback, request->url(), |
| 579 base::RetainedRef(auth_info), base::RetainedRef(handler), | 627 base::RetainedRef(auth_info), base::RetainedRef(handler), |
| 580 is_main_frame)); | 628 is_main_frame)); |
| 581 return handler; | 629 return handler; |
| 582 } | 630 } |
| 583 | 631 |
| 584 // Get the signon_realm under which this auth info should be stored. | |
| 585 // | |
| 586 // The format of the signon_realm for proxy auth is: | |
| 587 // proxy-host/auth-realm | |
| 588 // The format of the signon_realm for server auth is: | |
| 589 // url-scheme://url-host[:url-port]/auth-realm | |
| 590 // | |
| 591 // Be careful when changing this function, since you could make existing | |
| 592 // saved logins un-retrievable. | |
| 593 std::string GetSignonRealm(const GURL& url, | |
| 594 const net::AuthChallengeInfo& auth_info) { | |
| 595 std::string signon_realm; | |
| 596 if (auth_info.is_proxy) { | |
| 597 signon_realm = auth_info.challenger.ToString(); | |
| 598 signon_realm.append("/"); | |
| 599 } else { | |
| 600 // Take scheme, host, and port from the url. | |
| 601 signon_realm = url.GetOrigin().spec(); | |
| 602 // This ends with a "/". | |
| 603 } | |
| 604 signon_realm.append(auth_info.realm); | |
| 605 return signon_realm; | |
| 606 } | |
| OLD | NEW |