LCOV - code coverage report
Current view: top level - httpd/auth/openid - OpenIdServiceImpl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 19 251 7.6 %
Date: 2022-11-19 15:00:39 Functions: 3 13 23.1 %

          Line data    Source code
       1             : // Copyright (C) 2009 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.httpd.auth.openid;
      16             : 
      17             : import com.google.common.flogger.FluentLogger;
      18             : import com.google.gerrit.common.Nullable;
      19             : import com.google.gerrit.common.PageLinks;
      20             : import com.google.gerrit.common.auth.openid.OpenIdUrls;
      21             : import com.google.gerrit.entities.Account;
      22             : import com.google.gerrit.entities.KeyUtil;
      23             : import com.google.gerrit.extensions.registration.DynamicItem;
      24             : import com.google.gerrit.extensions.restapi.Url;
      25             : import com.google.gerrit.httpd.CanonicalWebUrl;
      26             : import com.google.gerrit.httpd.ProxyProperties;
      27             : import com.google.gerrit.httpd.WebSession;
      28             : import com.google.gerrit.server.IdentifiedUser;
      29             : import com.google.gerrit.server.UrlEncoded;
      30             : import com.google.gerrit.server.account.AccountException;
      31             : import com.google.gerrit.server.account.AccountManager;
      32             : import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory;
      33             : import com.google.gerrit.server.auth.openid.OpenIdProviderPattern;
      34             : import com.google.gerrit.server.config.AuthConfig;
      35             : import com.google.gerrit.server.config.ConfigUtil;
      36             : import com.google.gerrit.server.config.GerritServerConfig;
      37             : import com.google.inject.Inject;
      38             : import com.google.inject.Provider;
      39             : import com.google.inject.Singleton;
      40             : import java.io.IOException;
      41             : import java.net.URL;
      42             : import java.util.List;
      43             : import java.util.Optional;
      44             : import java.util.concurrent.TimeUnit;
      45             : import javax.servlet.http.Cookie;
      46             : import javax.servlet.http.HttpServletRequest;
      47             : import javax.servlet.http.HttpServletResponse;
      48             : import org.eclipse.jgit.lib.Config;
      49             : import org.openid4java.consumer.ConsumerException;
      50             : import org.openid4java.consumer.ConsumerManager;
      51             : import org.openid4java.consumer.VerificationResult;
      52             : import org.openid4java.discovery.DiscoveryException;
      53             : import org.openid4java.discovery.DiscoveryInformation;
      54             : import org.openid4java.message.AuthRequest;
      55             : import org.openid4java.message.Message;
      56             : import org.openid4java.message.MessageException;
      57             : import org.openid4java.message.MessageExtension;
      58             : import org.openid4java.message.ParameterList;
      59             : import org.openid4java.message.ax.AxMessage;
      60             : import org.openid4java.message.ax.FetchRequest;
      61             : import org.openid4java.message.ax.FetchResponse;
      62             : import org.openid4java.message.pape.PapeMessage;
      63             : import org.openid4java.message.pape.PapeRequest;
      64             : import org.openid4java.message.pape.PapeResponse;
      65             : import org.openid4java.message.sreg.SRegMessage;
      66             : import org.openid4java.message.sreg.SRegRequest;
      67             : import org.openid4java.message.sreg.SRegResponse;
      68             : import org.openid4java.util.HttpClientFactory;
      69             : 
      70             : @Singleton
      71             : class OpenIdServiceImpl {
      72          99 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      73             : 
      74             :   static final String RETURN_URL = "OpenID";
      75             : 
      76             :   private static final String P_MODE = "gerrit.mode";
      77             :   private static final String P_TOKEN = "gerrit.token";
      78             :   private static final String P_REMEMBER = "gerrit.remember";
      79             :   private static final String P_CLAIMED = "gerrit.claimed";
      80             :   private static final int LASTID_AGE = 365 * 24 * 60 * 60; // seconds
      81             : 
      82             :   private static final String OPENID_MODE = "openid.mode";
      83             :   private static final String OMODE_CANCEL = "cancel";
      84             : 
      85             :   private static final String SCHEMA_EMAIL = "http://schema.openid.net/contact/email";
      86             :   private static final String SCHEMA_FIRSTNAME = "http://schema.openid.net/namePerson/first";
      87             :   private static final String SCHEMA_LASTNAME = "http://schema.openid.net/namePerson/last";
      88             : 
      89             :   private final DynamicItem<WebSession> webSession;
      90             :   private final Provider<IdentifiedUser> identifiedUser;
      91             :   private final CanonicalWebUrl urlProvider;
      92             :   private final AccountManager accountManager;
      93             :   private final ConsumerManager manager;
      94             :   private final List<OpenIdProviderPattern> allowedOpenIDs;
      95             :   private final List<String> openIdDomains;
      96             :   private final ExternalIdKeyFactory externalIdKeyFactory;
      97             :   private final com.google.gerrit.server.account.AuthRequest.Factory authRequestFactory;
      98             : 
      99             :   /** Maximum age, in seconds, before forcing re-authentication of account. */
     100             :   private final int papeMaxAuthAge;
     101             : 
     102             :   @Inject
     103             :   OpenIdServiceImpl(
     104             :       DynamicItem<WebSession> cf,
     105             :       Provider<IdentifiedUser> iu,
     106             :       CanonicalWebUrl up,
     107             :       @GerritServerConfig Config config,
     108             :       AuthConfig ac,
     109             :       AccountManager am,
     110             :       ProxyProperties proxyProperties,
     111             :       ExternalIdKeyFactory externalIdKeyFactory,
     112          99 :       com.google.gerrit.server.account.AuthRequest.Factory authRequestFactory) {
     113             : 
     114          99 :     if (proxyProperties.getProxyUrl() != null) {
     115           0 :       final org.openid4java.util.ProxyProperties proxy = new org.openid4java.util.ProxyProperties();
     116           0 :       URL url = proxyProperties.getProxyUrl();
     117           0 :       proxy.setProxyHostName(url.getHost());
     118           0 :       proxy.setProxyPort(url.getPort());
     119           0 :       proxy.setUserName(proxyProperties.getUsername());
     120           0 :       proxy.setPassword(proxyProperties.getPassword());
     121           0 :       HttpClientFactory.setProxyProperties(proxy);
     122             :     }
     123             : 
     124          99 :     webSession = cf;
     125          99 :     identifiedUser = iu;
     126          99 :     urlProvider = up;
     127          99 :     accountManager = am;
     128          99 :     manager = new ConsumerManager();
     129          99 :     allowedOpenIDs = ac.getAllowedOpenIDs();
     130          99 :     openIdDomains = ac.getOpenIdDomains();
     131          99 :     papeMaxAuthAge =
     132             :         (int)
     133          99 :             ConfigUtil.getTimeUnit(
     134             :                 config, //
     135             :                 "auth",
     136             :                 null,
     137             :                 "maxOpenIdSessionAge",
     138             :                 -1,
     139             :                 TimeUnit.SECONDS);
     140          99 :     this.externalIdKeyFactory = externalIdKeyFactory;
     141          99 :     this.authRequestFactory = authRequestFactory;
     142          99 :   }
     143             : 
     144             :   @SuppressWarnings("unchecked")
     145             :   DiscoveryResult discover(
     146             :       HttpServletRequest req,
     147             :       String openidIdentifier,
     148             :       SignInMode mode,
     149             :       boolean remember,
     150             :       String returnToken) {
     151             :     final State state;
     152           0 :     state = init(req, openidIdentifier, mode, remember, returnToken);
     153           0 :     if (state == null) {
     154           0 :       return new DiscoveryResult(DiscoveryResult.Status.NO_PROVIDER);
     155             :     }
     156             : 
     157             :     final AuthRequest aReq;
     158             :     try {
     159           0 :       aReq = manager.authenticate(state.discovered, state.retTo.toString());
     160           0 :       logger.atFine().log("OpenID: openid-realm=%s", state.contextUrl);
     161           0 :       aReq.setRealm(state.contextUrl);
     162             : 
     163           0 :       if (requestRegistration(aReq)) {
     164           0 :         final SRegRequest sregReq = SRegRequest.createFetchRequest();
     165           0 :         sregReq.addAttribute("fullname", true);
     166           0 :         sregReq.addAttribute("email", true);
     167           0 :         aReq.addExtension(sregReq);
     168             : 
     169           0 :         final FetchRequest fetch = FetchRequest.createFetchRequest();
     170           0 :         fetch.addAttribute("FirstName", SCHEMA_FIRSTNAME, true);
     171           0 :         fetch.addAttribute("LastName", SCHEMA_LASTNAME, true);
     172           0 :         fetch.addAttribute("Email", SCHEMA_EMAIL, true);
     173           0 :         aReq.addExtension(fetch);
     174             :       }
     175             : 
     176           0 :       if (0 <= papeMaxAuthAge) {
     177           0 :         final PapeRequest pape = PapeRequest.createPapeRequest();
     178           0 :         pape.setMaxAuthAge(papeMaxAuthAge);
     179           0 :         aReq.addExtension(pape);
     180             :       }
     181           0 :     } catch (MessageException | ConsumerException e) {
     182           0 :       logger.atSevere().withCause(e).log("Cannot create OpenID redirect for %s", openidIdentifier);
     183           0 :       return new DiscoveryResult(DiscoveryResult.Status.ERROR);
     184           0 :     }
     185             : 
     186           0 :     return new DiscoveryResult(aReq.getDestinationUrl(false), aReq.getParameterMap());
     187             :   }
     188             : 
     189             :   private boolean requestRegistration(AuthRequest aReq) {
     190           0 :     if (AuthRequest.SELECT_ID.equals(aReq.getIdentity())) {
     191             :       // We don't know anything about the identity, as the provider
     192             :       // will offer the user a way to indicate their identity. Skip
     193             :       // any database query operation and assume we must ask for the
     194             :       // registration information, in case the identity is new to us.
     195             :       //
     196           0 :       return true;
     197             :     }
     198             : 
     199             :     // We might already have this account on file. Look for it.
     200             :     //
     201             :     try {
     202           0 :       return !accountManager.lookup(aReq.getIdentity()).isPresent();
     203           0 :     } catch (AccountException e) {
     204           0 :       logger.atWarning().withCause(e).log("Cannot determine if user account exists");
     205           0 :       return true;
     206             :     }
     207             :   }
     208             : 
     209             :   /** Called by {@link OpenIdLoginServlet} doGet, doPost */
     210             :   void doAuth(HttpServletRequest req, HttpServletResponse rsp) throws Exception {
     211           0 :     if (OMODE_CANCEL.equals(req.getParameter(OPENID_MODE))) {
     212           0 :       cancel(req, rsp);
     213           0 :       return;
     214             :     }
     215             : 
     216             :     // Process the authentication response.
     217             :     //
     218           0 :     final SignInMode mode = signInMode(req);
     219           0 :     final String openidIdentifier = req.getParameter("openid.identity");
     220           0 :     final String claimedIdentifier = req.getParameter(P_CLAIMED);
     221           0 :     final String returnToken = req.getParameter(P_TOKEN);
     222           0 :     final boolean remember = "1".equals(req.getParameter(P_REMEMBER));
     223             :     final String rediscoverIdentifier =
     224           0 :         claimedIdentifier != null ? claimedIdentifier : openidIdentifier;
     225             :     final State state;
     226             : 
     227           0 :     if (!isAllowedOpenID(rediscoverIdentifier)
     228           0 :         || !isAllowedOpenID(openidIdentifier)
     229           0 :         || (claimedIdentifier != null && !isAllowedOpenID(claimedIdentifier))) {
     230           0 :       cancelWithError(req, rsp, "Provider not allowed");
     231           0 :       return;
     232             :     }
     233             : 
     234           0 :     state = init(req, rediscoverIdentifier, mode, remember, returnToken);
     235           0 :     if (state == null) {
     236             :       // Re-discovery must have failed, we can't run a login.
     237             :       //
     238           0 :       cancel(req, rsp);
     239           0 :       return;
     240             :     }
     241             : 
     242           0 :     final String returnTo = req.getParameter("openid.return_to");
     243           0 :     if (returnTo != null && returnTo.contains("openid.rpnonce=")) {
     244             :       // Some providers (claimid.com) seem to embed these request
     245             :       // parameters into our return_to URL, and then give us them
     246             :       // in the return_to request parameter. But not all.
     247             :       //
     248           0 :       state.retTo.put("openid.rpnonce", req.getParameter("openid.rpnonce"));
     249           0 :       state.retTo.put("openid.rpsig", req.getParameter("openid.rpsig"));
     250             :     }
     251             : 
     252           0 :     final VerificationResult result =
     253           0 :         manager.verify(
     254           0 :             state.retTo.toString(), new ParameterList(req.getParameterMap()), state.discovered);
     255           0 :     if (result.getVerifiedId() == null /* authentication failure */) {
     256           0 :       if ("Nonce verification failed.".equals(result.getStatusMsg())) {
     257             :         // We might be suffering from clock skew on this system.
     258             :         //
     259           0 :         logger.atSevere().log(
     260             :             "OpenID failure: %s  Likely caused by clock skew on this server,"
     261             :                 + " install/configure NTP.",
     262           0 :             result.getStatusMsg());
     263           0 :         cancelWithError(req, rsp, result.getStatusMsg());
     264             : 
     265           0 :       } else if (result.getStatusMsg() != null) {
     266             :         // Authentication failed.
     267             :         //
     268           0 :         logger.atSevere().log("OpenID failure: %s", result.getStatusMsg());
     269           0 :         cancelWithError(req, rsp, result.getStatusMsg());
     270             : 
     271             :       } else {
     272             :         // Assume authentication was canceled.
     273             :         //
     274           0 :         cancel(req, rsp);
     275             :       }
     276           0 :       return;
     277             :     }
     278             : 
     279           0 :     final Message authRsp = result.getAuthResponse();
     280           0 :     SRegResponse sregRsp = null;
     281           0 :     FetchResponse fetchRsp = null;
     282             : 
     283           0 :     if (0 <= papeMaxAuthAge) {
     284             :       PapeResponse ext;
     285           0 :       boolean unsupported = false;
     286             : 
     287             :       try {
     288           0 :         ext = (PapeResponse) authRsp.getExtension(PapeMessage.OPENID_NS_PAPE);
     289           0 :       } catch (MessageException err) {
     290             :         // Far too many providers are unable to provide PAPE extensions
     291             :         // right now. Instead of blocking all of them log the error and
     292             :         // let the authentication complete anyway.
     293             :         //
     294           0 :         logger.atSevere().withCause(err).log("Invalid PAPE response from %s", openidIdentifier);
     295           0 :         unsupported = true;
     296           0 :         ext = null;
     297           0 :       }
     298           0 :       if (!unsupported && ext == null) {
     299           0 :         logger.atSevere().log("No PAPE extension response from %s", openidIdentifier);
     300           0 :         cancelWithError(req, rsp, "OpenID provider does not support PAPE.");
     301           0 :         return;
     302             :       }
     303             :     }
     304             : 
     305           0 :     if (authRsp.hasExtension(SRegMessage.OPENID_NS_SREG)) {
     306           0 :       final MessageExtension ext = authRsp.getExtension(SRegMessage.OPENID_NS_SREG);
     307           0 :       if (ext instanceof SRegResponse) {
     308           0 :         sregRsp = (SRegResponse) ext;
     309             :       }
     310             :     }
     311             : 
     312           0 :     if (authRsp.hasExtension(AxMessage.OPENID_NS_AX)) {
     313           0 :       final MessageExtension ext = authRsp.getExtension(AxMessage.OPENID_NS_AX);
     314           0 :       if (ext instanceof FetchResponse) {
     315           0 :         fetchRsp = (FetchResponse) ext;
     316             :       }
     317             :     }
     318             : 
     319           0 :     final com.google.gerrit.server.account.AuthRequest areq =
     320           0 :         authRequestFactory.create(externalIdKeyFactory.parse(openidIdentifier));
     321             : 
     322           0 :     if (sregRsp != null) {
     323           0 :       areq.setDisplayName(sregRsp.getAttributeValue("fullname"));
     324           0 :       areq.setEmailAddress(sregRsp.getAttributeValue("email"));
     325             : 
     326           0 :     } else if (fetchRsp != null) {
     327           0 :       final String firstName = fetchRsp.getAttributeValue("FirstName");
     328           0 :       final String lastName = fetchRsp.getAttributeValue("LastName");
     329           0 :       final StringBuilder n = new StringBuilder();
     330           0 :       if (firstName != null && firstName.length() > 0) {
     331           0 :         n.append(firstName);
     332             :       }
     333           0 :       if (lastName != null && lastName.length() > 0) {
     334           0 :         if (n.length() > 0) {
     335           0 :           n.append(' ');
     336             :         }
     337           0 :         n.append(lastName);
     338             :       }
     339           0 :       areq.setDisplayName(n.length() > 0 ? n.toString() : null);
     340           0 :       areq.setEmailAddress(fetchRsp.getAttributeValue("Email"));
     341             :     }
     342             : 
     343           0 :     if (openIdDomains != null && !openIdDomains.isEmpty()) {
     344             :       // Administrator limited email domains, which can be used for OpenID.
     345             :       // Login process will only work if the passed email matches one
     346             :       // of these domains.
     347             :       //
     348           0 :       final String email = areq.getEmailAddress();
     349           0 :       int emailAtIndex = email.lastIndexOf('@');
     350           0 :       if (emailAtIndex >= 0 && emailAtIndex < email.length() - 1) {
     351           0 :         final String emailDomain = email.substring(emailAtIndex);
     352             : 
     353           0 :         boolean match = false;
     354           0 :         for (String domain : openIdDomains) {
     355           0 :           if (emailDomain.equalsIgnoreCase(domain)) {
     356           0 :             match = true;
     357           0 :             break;
     358             :           }
     359           0 :         }
     360             : 
     361           0 :         if (!match) {
     362           0 :           logger.atSevere().log("Domain disallowed: %s", emailDomain);
     363           0 :           cancelWithError(req, rsp, "Domain disallowed");
     364           0 :           return;
     365             :         }
     366             :       }
     367             :     }
     368             : 
     369           0 :     if (claimedIdentifier != null) {
     370             :       // The user used a claimed identity which has delegated to the verified
     371             :       // identity we have in our AuthRequest above. We still should have a
     372             :       // link between the two, so set one up if not present.
     373             :       //
     374           0 :       Optional<Account.Id> claimedId = accountManager.lookup(claimedIdentifier);
     375           0 :       Optional<Account.Id> actualId = accountManager.lookup(areq.getExternalIdKey().get());
     376             : 
     377           0 :       if (claimedId.isPresent() && actualId.isPresent()) {
     378           0 :         if (claimedId.get().equals(actualId.get())) {
     379             :           // Both link to the same account, that's what we expected.
     380             :         } else {
     381             :           // This is (for now) a fatal error. There are two records
     382             :           // for what might be the same user.
     383             :           //
     384           0 :           logger.atSevere().log(
     385             :               "OpenID accounts disagree over user identity:\n"
     386             :                   + "  Claimed ID: %s is %s\n"
     387             :                   + "  Delgate ID: %s is %s",
     388           0 :               claimedId.get(), claimedIdentifier, actualId.get(), areq.getExternalIdKey());
     389           0 :           cancelWithError(req, rsp, "Contact site administrator");
     390           0 :           return;
     391             :         }
     392             : 
     393           0 :       } else if (!claimedId.isPresent() && actualId.isPresent()) {
     394             :         // Older account, the actual was already created but the claimed
     395             :         // was missing due to a bug in Gerrit. Link the claimed.
     396             :         //
     397           0 :         final com.google.gerrit.server.account.AuthRequest linkReq =
     398           0 :             authRequestFactory.create(externalIdKeyFactory.parse(claimedIdentifier));
     399           0 :         linkReq.setDisplayName(areq.getDisplayName());
     400           0 :         linkReq.setEmailAddress(areq.getEmailAddress());
     401           0 :         accountManager.link(actualId.get(), linkReq);
     402             : 
     403           0 :       } else if (claimedId.isPresent() && !actualId.isPresent()) {
     404             :         // Claimed account already exists, but it smells like the user has
     405             :         // changed their delegate to point to a different provider. Link
     406             :         // the new provider.
     407             :         //
     408           0 :         accountManager.link(claimedId.get(), areq);
     409             : 
     410             :       } else {
     411             :         // Both are null, we are going to create a new account below.
     412             :       }
     413             :     }
     414             : 
     415             :     try {
     416             :       final com.google.gerrit.server.account.AuthResult arsp;
     417           0 :       switch (mode) {
     418             :         case REGISTER:
     419             :         case SIGN_IN:
     420           0 :           arsp = accountManager.authenticate(areq);
     421             : 
     422           0 :           final Cookie lastId = new Cookie(OpenIdUrls.LASTID_COOKIE, "");
     423           0 :           lastId.setPath(req.getContextPath() + "/login/");
     424           0 :           if (remember) {
     425           0 :             lastId.setValue(rediscoverIdentifier);
     426           0 :             lastId.setMaxAge(LASTID_AGE);
     427             :           } else {
     428           0 :             lastId.setMaxAge(0);
     429             :           }
     430           0 :           rsp.addCookie(lastId);
     431           0 :           webSession.get().login(arsp, remember);
     432           0 :           if (arsp.isNew() && claimedIdentifier != null) {
     433           0 :             final com.google.gerrit.server.account.AuthRequest linkReq =
     434           0 :                 authRequestFactory.create(externalIdKeyFactory.parse(claimedIdentifier));
     435           0 :             linkReq.setDisplayName(areq.getDisplayName());
     436           0 :             linkReq.setEmailAddress(areq.getEmailAddress());
     437           0 :             accountManager.link(arsp.getAccountId(), linkReq);
     438             :           }
     439           0 :           callback(arsp.isNew(), req, rsp);
     440           0 :           break;
     441             : 
     442             :         case LINK_IDENTIY:
     443             :           {
     444           0 :             arsp = accountManager.link(identifiedUser.get().getAccountId(), areq);
     445           0 :             webSession.get().login(arsp, remember);
     446           0 :             callback(false, req, rsp);
     447             :             break;
     448             :           }
     449             :       }
     450           0 :     } catch (AccountException e) {
     451           0 :       logger.atSevere().withCause(e).log("OpenID authentication failure");
     452           0 :       cancelWithError(req, rsp, "Contact site administrator");
     453           0 :     }
     454           0 :   }
     455             : 
     456             :   private boolean isSignIn(SignInMode mode) {
     457           0 :     switch (mode) {
     458             :       case SIGN_IN:
     459             :       case REGISTER:
     460           0 :         return true;
     461             :       case LINK_IDENTIY:
     462             :       default:
     463           0 :         return false;
     464             :     }
     465             :   }
     466             : 
     467             :   private static SignInMode signInMode(HttpServletRequest req) {
     468             :     try {
     469           0 :       return SignInMode.valueOf(req.getParameter(P_MODE));
     470           0 :     } catch (RuntimeException e) {
     471           0 :       return SignInMode.SIGN_IN;
     472             :     }
     473             :   }
     474             : 
     475             :   private void callback(final boolean isNew, HttpServletRequest req, HttpServletResponse rsp)
     476             :       throws IOException {
     477           0 :     String token = req.getParameter(P_TOKEN);
     478           0 :     if (token == null || token.isEmpty() || token.startsWith("/SignInFailure,")) {
     479           0 :       token = PageLinks.MINE;
     480             :     }
     481             : 
     482           0 :     final StringBuilder rdr = new StringBuilder();
     483           0 :     rdr.append(urlProvider.get(req));
     484           0 :     String nextToken = Url.decode(token);
     485           0 :     String registerUri = PageLinks.REGISTER + "/";
     486           0 :     if (isNew && !token.startsWith(registerUri)) {
     487           0 :       rdr.append('#' + registerUri);
     488           0 :       if (nextToken.startsWith("#")) {
     489             :         // Need to strip the leading # off the token to fix registration page redirect
     490           0 :         nextToken = nextToken.substring(1);
     491             :       }
     492             :     }
     493           0 :     rdr.append(nextToken);
     494           0 :     rsp.sendRedirect(rdr.toString());
     495           0 :   }
     496             : 
     497             :   private void cancel(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
     498           0 :     if (isSignIn(signInMode(req))) {
     499           0 :       webSession.get().logout();
     500             :     }
     501           0 :     callback(false, req, rsp);
     502           0 :   }
     503             : 
     504             :   private void cancelWithError(
     505             :       final HttpServletRequest req, HttpServletResponse rsp, String errorDetail)
     506             :       throws IOException {
     507           0 :     final SignInMode mode = signInMode(req);
     508           0 :     if (isSignIn(mode)) {
     509           0 :       webSession.get().logout();
     510             :     }
     511           0 :     final StringBuilder rdr = new StringBuilder();
     512           0 :     rdr.append(urlProvider.get(req));
     513           0 :     rdr.append('#');
     514           0 :     rdr.append("SignInFailure");
     515           0 :     rdr.append(',');
     516           0 :     rdr.append(mode.name());
     517           0 :     rdr.append(',');
     518           0 :     rdr.append(errorDetail != null ? KeyUtil.encode(errorDetail) : "");
     519           0 :     rsp.sendRedirect(rdr.toString());
     520           0 :   }
     521             : 
     522             :   @Nullable
     523             :   private State init(
     524             :       HttpServletRequest req,
     525             :       final String openidIdentifier,
     526             :       final SignInMode mode,
     527             :       final boolean remember,
     528             :       final String returnToken) {
     529             :     final List<?> list;
     530             :     try {
     531           0 :       list = manager.discover(openidIdentifier);
     532           0 :     } catch (DiscoveryException e) {
     533           0 :       logger.atSevere().withCause(e).log("Cannot discover OpenID %s", openidIdentifier);
     534           0 :       return null;
     535           0 :     }
     536           0 :     if (list == null || list.isEmpty()) {
     537           0 :       return null;
     538             :     }
     539             : 
     540           0 :     final String contextUrl = urlProvider.get(req);
     541           0 :     final DiscoveryInformation discovered = manager.associate(list);
     542           0 :     final UrlEncoded retTo = new UrlEncoded(contextUrl + RETURN_URL);
     543           0 :     retTo.put(P_MODE, mode.name());
     544           0 :     if (returnToken != null && returnToken.length() > 0) {
     545           0 :       retTo.put(P_TOKEN, returnToken);
     546             :     }
     547           0 :     if (remember) {
     548           0 :       retTo.put(P_REMEMBER, "1");
     549             :     }
     550           0 :     if (discovered.hasClaimedIdentifier()) {
     551           0 :       retTo.put(P_CLAIMED, discovered.getClaimedIdentifier().getIdentifier());
     552             :     }
     553           0 :     return new State(discovered, retTo, contextUrl);
     554             :   }
     555             : 
     556             :   boolean isAllowedOpenID(String id) {
     557          99 :     for (OpenIdProviderPattern pattern : allowedOpenIDs) {
     558          99 :       if (pattern.matches(id)) {
     559          99 :         return true;
     560             :       }
     561          99 :     }
     562           0 :     return false;
     563             :   }
     564             : 
     565             :   private static class State {
     566             :     final DiscoveryInformation discovered;
     567             :     final UrlEncoded retTo;
     568             :     final String contextUrl;
     569             : 
     570           0 :     State(DiscoveryInformation d, UrlEncoded r, String c) {
     571           0 :       discovered = d;
     572           0 :       retTo = r;
     573           0 :       contextUrl = c;
     574           0 :     }
     575             :   }
     576             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750