@Override
  protected Event doExecute(final RequestContext context) throws Exception {
    final Service service = WebUtils.getService(context);
    // No service == plain /login request. Return success indicating transition to the login form
    if (service == null) {
      return success();
    }
    final RegisteredService registeredService = this.servicesManager.findServiceBy(service);

    if (registeredService == null) {
      logger.warn(
          "Unauthorized Service Access for Service: [ {} ] - service is not defined in the service registry.",
          service.getId());
      throw new UnauthorizedServiceException();
    } else if (!registeredService.isEnabled()) {
      logger.warn(
          "Unauthorized Service Access for Service: [ {} ] - service is not enabled in the service registry.",
          service.getId());
      if (registeredService instanceof RegisteredServiceWithAttributes) {
        String disabledServiceUrl =
            (String)
                RegisteredServiceWithAttributes.class
                    .cast(registeredService)
                    .getExtraAttributes()
                    .get(DISABLED_SERVICE_URL_ATTRIBUTE);
        if (disabledServiceUrl != null) {
          context.getRequestScope().put(DISABLED_SERVICE_URL_ATTRIBUTE, disabledServiceUrl);
          return no();
        }
      }
      throw new UnauthorizedServiceException();
    }
    return success();
  }
  @Override
  protected Object formBackingObject(final HttpServletRequest request) throws Exception {
    final String id = request.getParameter("id");

    if (!StringUtils.hasText(id)) {
      // create a default RegisteredServiceImpl object if an explicit class isn't set
      final Object service;
      if (this.getCommandClass() != null) {
        service = this.createCommand();
      } else {
        service = new RegisteredServiceImpl();
      }
      logger.debug("Created new service of type " + service.getClass().getName());
      return service;
    }

    final RegisteredService service = this.servicesManager.findServiceBy(Long.parseLong(id));

    if (service != null) {
      logger.debug("Loaded service " + service.getServiceId());
    } else {
      logger.debug("Invalid service id specified.");
    }

    return service;
  }
  @Override
  protected Set<Event> resolveInternal(final RequestContext context) {
    final RegisteredService service = WebUtils.getRegisteredService(context);
    final Authentication authentication = WebUtils.getAuthentication(context);

    if (service == null || authentication == null) {
      logger.debug("No service or authentication is available to determine event for principal");
      return null;
    }
    final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
    final String[] values = request.getParameterValues(this.parameterName);
    if (values != null && values.length > 0) {
      logger.debug("Received request parameter {} as {}", this.parameterName, values);

      final Map<String, MultifactorAuthenticationProvider> providerMap =
          getAllMultifactorAuthenticationProvidersFromApplicationContext();
      if (providerMap == null || providerMap.isEmpty()) {
        logger.warn(
            "No multifactor authentication providers are available in the application context");
        throw new AuthenticationException();
      }

      final Optional<MultifactorAuthenticationProvider> providerFound =
          providerMap
              .values()
              .stream()
              .filter(provider -> provider.getId().equals(values[0]))
              .findFirst();

      if (providerFound.isPresent()) {
        if (providerFound.get().verify(service)) {
          logger.debug(
              "Attempting to build an event based on the authentication provider [{}] and service [{}]",
              providerFound.get(),
              service.getName());
          final Event event =
              validateEventIdForMatchingTransitionInContext(
                  providerFound.get().getId(),
                  context,
                  buildEventAttributeMap(
                      authentication.getPrincipal(), service, providerFound.get()));
          return ImmutableSet.of(event);
        }
        logger.warn(
            "Located multifactor provider {}, yet the provider cannot be reached or verified",
            providerFound.get());
        return null;
      } else {
        logger.warn("No multifactor provider could be found for request parameter {}", values);
        throw new AuthenticationException();
      }
    }
    logger.debug("No value could be found for request parameter {}", this.parameterName);
    return null;
  }
  @Audit(
      action = "PROXY_GRANTING_TICKET",
      actionResolverName = "GRANT_PROXY_GRANTING_TICKET_RESOLVER",
      resourceResolverName = "GRANT_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER")
  @Timed(name = "GRANT_PROXY_GRANTING_TICKET_TIMER")
  @Metered(name = "GRANT_PROXY_GRANTING_TICKET_METER")
  @Counted(name = "GRANT_PROXY_GRANTING_TICKET_COUNTER", monotonic = true)
  @Transactional(readOnly = false)
  @Override
  public TicketGrantingTicket delegateTicketGrantingTicket(
      final String serviceTicketId, final Credential... credentials)
      throws AuthenticationException, TicketException {

    final ServiceTicket serviceTicket =
        this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class);

    if (serviceTicket == null || serviceTicket.isExpired()) {
      logger.debug(
          "ServiceTicket [{}] has expired or cannot be found in the ticket registry",
          serviceTicketId);
      throw new InvalidTicketException(serviceTicketId);
    }

    final RegisteredService registeredService =
        this.servicesManager.findServiceBy(serviceTicket.getService());

    verifyRegisteredServiceProperties(registeredService, serviceTicket.getService());

    if (!registeredService.getProxyPolicy().isAllowedToProxy()) {
      logger.warn(
          "ServiceManagement: Service [{}] attempted to proxy, but is not allowed.",
          serviceTicket.getService().getId());
      throw new UnauthorizedProxyingException();
    }

    final Authentication authentication = this.authenticationManager.authenticate(credentials);

    final String pgtId =
        this.ticketGrantingTicketUniqueTicketIdGenerator.getNewTicketId(
            TicketGrantingTicket.PROXY_GRANTING_TICKET_PREFIX);
    final TicketGrantingTicket proxyGrantingTicket =
        serviceTicket.grantTicketGrantingTicket(
            pgtId, authentication, this.ticketGrantingTicketExpirationPolicy);

    logger.debug(
        "Generated proxy granting ticket [{}] based off of [{}]",
        proxyGrantingTicket,
        serviceTicketId);
    this.ticketRegistry.addTicket(proxyGrantingTicket);

    return proxyGrantingTicket;
  }
  /**
   * Construct SAML response. <a href="http://bit.ly/1uI8Ggu">See this reference for more info.</a>
   *
   * @param service the service
   * @return the SAML response
   */
  protected String constructSamlResponse(final GoogleAccountsService service) {
    final DateTime currentDateTime =
        DateTime.parse(new ISOStandardDateFormat().getCurrentDateAndTime());
    final DateTime notBeforeIssueInstant = DateTime.parse("2003-04-17T00:46:02Z");

    /*
     * Must be looked up directly from the context
     * because the services manager is not serializable
     * and cannot be class field.
     */
    final ApplicationContext context = ApplicationContextProvider.getApplicationContext();
    final ServicesManager servicesManager =
        context.getBean("servicesManager", ServicesManager.class);
    final RegisteredService registeredService = servicesManager.findServiceBy(service);
    final String userId =
        registeredService
            .getUsernameAttributeProvider()
            .resolveUsername(service.getPrincipal(), service);

    final org.opensaml.saml.saml2.core.Response response =
        samlObjectBuilder.newResponse(
            samlObjectBuilder.generateSecureRandomId(), currentDateTime, service.getId(), service);
    response.setStatus(samlObjectBuilder.newStatus(StatusCode.SUCCESS, null));

    final AuthnStatement authnStatement =
        samlObjectBuilder.newAuthnStatement(AuthnContext.PASSWORD_AUTHN_CTX, currentDateTime);
    final Assertion assertion =
        samlObjectBuilder.newAssertion(
            authnStatement,
            "https://www.opensaml.org/IDP",
            notBeforeIssueInstant,
            samlObjectBuilder.generateSecureRandomId());

    final Conditions conditions =
        samlObjectBuilder.newConditions(notBeforeIssueInstant, currentDateTime, service.getId());
    assertion.setConditions(conditions);

    final Subject subject =
        samlObjectBuilder.newSubject(
            NameID.EMAIL, userId, service.getId(), currentDateTime, service.getRequestId());
    assertion.setSubject(subject);

    response.getAssertions().add(assertion);

    final StringWriter writer = new StringWriter();
    samlObjectBuilder.marshalSamlXmlObject(response, writer);

    final String result = writer.toString();
    logger.debug("Generated Google SAML response: {}", result);
    return result;
  }
  @Audit(
      action = "PROXY_GRANTING_TICKET",
      actionResolverName = "GRANT_PROXY_GRANTING_TICKET_RESOLVER",
      resourceResolverName = "GRANT_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER")
  @Profiled(tag = "GRANT_PROXY_GRANTING_TICKET", logFailuresSeparately = false)
  @Transactional(readOnly = false)
  @Override
  public String delegateTicketGrantingTicket(
      final String serviceTicketId, final Credential... credentials)
      throws AuthenticationException, TicketException {

    Assert.notNull(serviceTicketId, "serviceTicketId cannot be null");
    Assert.notNull(credentials, "credentials cannot be null");

    final ServiceTicket serviceTicket =
        this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class);

    if (serviceTicket == null || serviceTicket.isExpired()) {
      logger.debug(
          "ServiceTicket [{}] has expired or cannot be found in the ticket registry",
          serviceTicketId);
      throw new InvalidTicketException(serviceTicketId);
    }

    final RegisteredService registeredService =
        this.servicesManager.findServiceBy(serviceTicket.getService());

    verifyRegisteredServiceProperties(registeredService, serviceTicket.getService());

    if (!registeredService.isAllowedToProxy()) {
      logger.warn(
          "ServiceManagement: Service [{}] attempted to proxy, but is not allowed.",
          serviceTicket.getService().getId());
      throw new UnauthorizedProxyingException();
    }

    final Authentication authentication = this.authenticationManager.authenticate(credentials);

    final TicketGrantingTicket ticketGrantingTicket =
        serviceTicket.grantTicketGrantingTicket(
            this.ticketGrantingTicketUniqueTicketIdGenerator.getNewTicketId(
                TicketGrantingTicket.PREFIX),
            authentication,
            this.ticketGrantingTicketExpirationPolicy);

    this.ticketRegistry.addTicket(ticketGrantingTicket);

    return ticketGrantingTicket.getId();
  }
  public CasShibRegisteredService findServiceByAppName(String appName)
      throws CasShibServiceRegistrarException {
    if (!isInitialized) {
      // this shouldn't happen here, but just in case
      initialize();
    }

    for (RegisteredService entry : servicesManager.getAllServices()) {
      if (entry instanceof CasShibRegisteredService && entry.getName().equals(appName)) {
        return ((CasShibRegisteredService) entry);
      }
    }

    return null;
  }
  private void clearAllServices() {
    final Collection<RegisteredService> col =
        ((OAuth20WrapperController) oauth20WrapperController).getServicesManager().getAllServices();

    for (final RegisteredService r : col) {
      ((OAuth20WrapperController) oauth20WrapperController).getServicesManager().delete(r.getId());
    }
  }
  @Override
  protected Event doExecute(final RequestContext context) throws Exception {
    final Service service = WebUtils.getService(context);

    if (service == null) {
      logger.debug("No service found in the request context, so resuming normally.");
      return success();
    }

    final RegisteredService registeredService = this.servicesManager.findServiceBy(service);

    if (registeredService == null) {
      logger.warn(
          "Unauthorized Service Access for Service: [{}] - service is not defined in the service registry.",
          service.getId());
      throw new UnauthorizedServiceException();
    }

    if (!registeredService.isEnabled()) {
      logger.warn(
          "Unauthorized Service Access for Service: [{}] - service is not enabled in the service registry.",
          service.getId());
      throw new UnauthorizedServiceException();
    }

    if (registeredService instanceof RegisteredServiceWithAttributes) {
      final RegisteredServiceWithAttributes regSvcWithAttr =
          RegisteredServiceWithAttributes.class.cast(registeredService);

      final String redirectToUrl =
          (String) regSvcWithAttr.getExtraAttributes().get(REDIRECT_TO_URL_ATTRIBUTE);
      if (redirectToUrl != null
          && this.redirectionAdvisor.shouldRedirectServiceRequest(
              context, regSvcWithAttr, redirectToUrl)) {
        logger.info("Redirecting to url [{}] for service [{}]", redirectToUrl, service.getId());
        context.getRequestScope().put(REDIRECT_TO_URL_ATTRIBUTE, redirectToUrl);
        return yes();
      }
    }

    logger.debug(
        "No redirect url is configured, or redirection for service [{}] is not needed",
        service.getId());
    return success();
  }
  /**
   * {@inheritDoc} Adds the service to the ServiceRegistry via the ServiceRegistryManager.
   *
   * @see
   *     org.springframework.web.servlet.mvc.SimpleFormController#onSubmit(javax.servlet.http.HttpServletRequest,
   *     javax.servlet.http.HttpServletResponse, java.lang.Object,
   *     org.springframework.validation.BindException)
   */
  @Override
  protected ModelAndView onSubmit(
      final HttpServletRequest request,
      final HttpServletResponse response,
      final Object command,
      final BindException errors)
      throws Exception {
    RegisteredService service = (RegisteredService) command;

    // only change object class if there isn't an explicit RegisteredService class set
    if (this.getCommandClass() == null) {
      // CAS-1071
      // Treat _new_ patterns starting with ^ character as a regular expression
      if (service.getId() == RegisteredService.INITIAL_IDENTIFIER_VALUE
          && service.getServiceId().startsWith("^")) {
        logger.debug("Detected regular expression starting with ^");
        final RegexRegisteredService regexService = new RegexRegisteredService();
        regexService.copyFrom(service);
        service = regexService;
      }
    }
    this.servicesManager.save(service);
    logger.info("Saved changes to service " + service.getId());

    final ModelAndView modelAndView =
        new ModelAndView(new RedirectView("manage.html#" + service.getId(), true));
    modelAndView.addObject("action", "add");
    modelAndView.addObject("id", service.getId());

    return modelAndView;
  }
  /**
   * Determines the principal id to use for a {@link RegisteredService} using the following rules:
   *
   * <ul>
   *   <li>If the service is marked to allow anonymous access, a persistent id is returned.
   *   <li>If the {@link org.jasig.cas.services.RegisteredService#getUsernameAttribute()} is blank,
   *       then the default principal id is returned.
   *   <li>If the username attribute is available as part of the principal's attributes, the
   *       corresponding attribute value will be returned.
   *   <li>Otherwise, the default principal's id is returned as the username attribute with an
   *       additional warning.
   * </ul>
   *
   * @param principal The principal object to be validated and constructed
   * @param registeredService Requesting service for which a principal is being validated.
   * @param serviceTicket An instance of the service ticket used for validation
   * @return The principal id to use for the requesting registered service
   */
  private String determinePrincipalIdForRegisteredService(
      final Principal principal,
      final RegisteredService registeredService,
      final ServiceTicket serviceTicket) {
    String principalId = null;
    final String serviceUsernameAttribute = registeredService.getUsernameAttribute();

    if (registeredService.isAnonymousAccess()) {
      principalId = this.persistentIdGenerator.generate(principal, serviceTicket.getService());
    } else if (StringUtils.isBlank(serviceUsernameAttribute)) {
      principalId = principal.getId();
    } else {
      if (principal.getAttributes().containsKey(serviceUsernameAttribute)) {
        principalId = principal.getAttributes().get(serviceUsernameAttribute).toString();
      } else {
        principalId = principal.getId();
        final Object[] errorLogParameters =
            new Object[] {
              principalId,
              registeredService.getUsernameAttribute(),
              principal.getAttributes(),
              registeredService.getServiceId(),
              principalId
            };
        logger.warn(
            "Principal [{}] did not have attribute [{}] among attributes [{}] so CAS cannot "
                + "provide on the validation response the user attribute the registered service [{}] expects. "
                + "CAS will instead return the default username attribute [{}]",
            errorLogParameters);
      }
    }

    logger.debug(
        "Principal id to return for service [{}] is [{}]. The default principal id is [{}].",
        new Object[] {registeredService.getName(), principal.getId(), principalId});
    return principalId;
  }
  /**
   * Ensure that the service is found and enabled in the service registry.
   *
   * @param registeredService the located entry in the registry
   * @param service authenticating service
   * @throws UnauthorizedServiceException
   */
  private void verifyRegisteredServiceProperties(
      final RegisteredService registeredService, final Service service) {
    if (registeredService == null) {
      final String msg =
          String.format(
              "ServiceManagement: Unauthorized Service Access. "
                  + "Service [%s] is not found in service registry.",
              service.getId());
      logger.warn(msg);
      throw new UnauthorizedServiceException(
          UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg);
    }
    if (!registeredService.getAccessStrategy().isServiceAccessAllowed()) {
      final String msg =
          String.format(
              "ServiceManagement: Unauthorized Service Access. "
                  + "Service [%s] is not enabled in service registry.",
              service.getId());

      logger.warn(msg);
      throw new UnauthorizedServiceException(
          UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg);
    }
  }
  @Audit(
      action = "SERVICE_TICKET_VALIDATE",
      actionResolverName = "VALIDATE_SERVICE_TICKET_RESOLVER",
      resourceResolverName = "VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER")
  @Timed(name = "VALIDATE_SERVICE_TICKET_TIMER")
  @Metered(name = "VALIDATE_SERVICE_TICKET_METER")
  @Counted(name = "VALIDATE_SERVICE_TICKET_COUNTER", monotonic = true)
  @Transactional(readOnly = false)
  @Override
  public Assertion validateServiceTicket(final String serviceTicketId, final Service service)
      throws TicketException {
    final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
    verifyRegisteredServiceProperties(registeredService, service);

    final ServiceTicket serviceTicket =
        this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class);

    if (serviceTicket == null) {
      logger.info("Service ticket [{}] does not exist.", serviceTicketId);
      throw new InvalidTicketException(serviceTicketId);
    }

    try {
      synchronized (serviceTicket) {
        if (serviceTicket.isExpired()) {
          logger.info("ServiceTicket [{}] has expired.", serviceTicketId);
          throw new InvalidTicketException(serviceTicketId);
        }

        if (!serviceTicket.isValidFor(service)) {
          logger.error(
              "Service ticket [{}] with service [{}] does not match supplied service [{}]",
              serviceTicketId,
              serviceTicket.getService().getId(),
              service);
          throw new UnrecognizableServiceForServiceTicketValidationException(
              serviceTicket.getService());
        }
      }

      final TicketGrantingTicket root = serviceTicket.getGrantingTicket().getRoot();
      final Authentication authentication =
          getAuthenticationSatisfiedByPolicy(
              root, new ServiceContext(serviceTicket.getService(), registeredService));
      final Principal principal = authentication.getPrincipal();

      final AttributeReleasePolicy attributePolicy = registeredService.getAttributeReleasePolicy();
      logger.debug(
          "Attribute policy [{}] is associated with service [{}]",
          attributePolicy,
          registeredService);

      @SuppressWarnings("unchecked")
      final Map<String, Object> attributesToRelease =
          attributePolicy != null
              ? attributePolicy.getAttributes(principal)
              : Collections.EMPTY_MAP;

      final String principalId =
          registeredService.getUsernameAttributeProvider().resolveUsername(principal, service);
      final Principal modifiedPrincipal =
          this.principalFactory.createPrincipal(principalId, attributesToRelease);
      final AuthenticationBuilder builder = AuthenticationBuilder.newInstance(authentication);
      builder.setPrincipal(modifiedPrincipal);

      return new ImmutableAssertion(
          builder.build(),
          serviceTicket.getGrantingTicket().getChainedAuthentications(),
          serviceTicket.getService(),
          serviceTicket.isFromNewLogin());
    } finally {
      if (serviceTicket.isExpired()) {
        this.serviceTicketRegistry.deleteTicket(serviceTicketId);
      }
    }
  }
  @Audit(
      action = "SERVICE_TICKET",
      actionResolverName = "GRANT_SERVICE_TICKET_RESOLVER",
      resourceResolverName = "GRANT_SERVICE_TICKET_RESOURCE_RESOLVER")
  @Timed(name = "GRANT_SERVICE_TICKET_TIMER")
  @Metered(name = "GRANT_SERVICE_TICKET_METER")
  @Counted(name = "GRANT_SERVICE_TICKET_COUNTER", monotonic = true)
  @Transactional(readOnly = false)
  @Override
  public ServiceTicket grantServiceTicket(
      final String ticketGrantingTicketId, final Service service, final Credential... credentials)
      throws AuthenticationException, TicketException {

    final TicketGrantingTicket ticketGrantingTicket =
        getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
    final RegisteredService registeredService = this.servicesManager.findServiceBy(service);

    verifyRegisteredServiceProperties(registeredService, service);
    final Set<Credential> sanitizedCredentials = sanitizeCredentials(credentials);

    Authentication currentAuthentication = null;
    if (sanitizedCredentials.size() > 0) {
      currentAuthentication =
          this.authenticationManager.authenticate(
              sanitizedCredentials.toArray(new Credential[] {}));
      final Authentication original = ticketGrantingTicket.getAuthentication();
      if (!currentAuthentication.getPrincipal().equals(original.getPrincipal())) {
        throw new MixedPrincipalException(
            currentAuthentication, currentAuthentication.getPrincipal(), original.getPrincipal());
      }
      ticketGrantingTicket.getSupplementalAuthentications().add(currentAuthentication);
    }

    if (currentAuthentication == null
        && !registeredService.getAccessStrategy().isServiceAccessAllowedForSso()) {
      logger.warn("ServiceManagement: Service [{}] is not allowed to use SSO.", service.getId());
      throw new UnauthorizedSsoServiceException();
    }

    final Service proxiedBy = ticketGrantingTicket.getProxiedBy();
    if (proxiedBy != null) {
      logger.debug(
          "TGT is proxied by [{}]. Locating proxy service in registry...", proxiedBy.getId());
      final RegisteredService proxyingService = servicesManager.findServiceBy(proxiedBy);

      if (proxyingService != null) {
        logger.debug("Located proxying service [{}] in the service registry", proxyingService);
        if (!proxyingService.getProxyPolicy().isAllowedToProxy()) {
          logger.warn(
              "Found proxying service {}, but it is not authorized to fulfill the proxy attempt made by {}",
              proxyingService.getId(),
              service.getId());
          throw new UnauthorizedProxyingException(
              "Proxying is not allowed for registered service " + registeredService.getId());
        }
      } else {
        logger.warn(
            "No proxying service found. Proxy attempt by service [{}] (registered service [{}]) is not allowed.",
            service.getId(),
            registeredService.getId());
        throw new UnauthorizedProxyingException(
            "Proxying is not allowed for registered service " + registeredService.getId());
      }
    } else {
      logger.trace("TGT is not proxied by another service");
    }

    // Perform security policy check by getting the authentication that satisfies the configured
    // policy
    // This throws if no suitable policy is found
    getAuthenticationSatisfiedByPolicy(
        ticketGrantingTicket, new ServiceContext(service, registeredService));

    final List<Authentication> authentications = ticketGrantingTicket.getChainedAuthentications();
    final Principal principal = authentications.get(authentications.size() - 1).getPrincipal();

    final Map<String, Object> principalAttrs =
        registeredService.getAttributeReleasePolicy().getAttributes(principal);
    if (!registeredService
        .getAccessStrategy()
        .doPrincipalAttributesAllowServiceAccess(principalAttrs)) {
      logger.warn(
          "ServiceManagement: Cannot grant service ticket because Service [{}] is not authorized for use by [{}].",
          service.getId(),
          principal);
      throw new UnauthorizedServiceForPrincipalException();
    }

    final String uniqueTicketIdGenKey = service.getClass().getName();
    logger.debug("Looking up service ticket id generator for [{}]", uniqueTicketIdGenKey);
    UniqueTicketIdGenerator serviceTicketUniqueTicketIdGenerator =
        this.uniqueTicketIdGeneratorsForService.get(uniqueTicketIdGenKey);
    if (serviceTicketUniqueTicketIdGenerator == null) {
      serviceTicketUniqueTicketIdGenerator = this.defaultServiceTicketIdGenerator;
      logger.debug(
          "Service ticket id generator not found for [{}]. Using the default generator...",
          uniqueTicketIdGenKey);
    }

    final String ticketPrefix =
        authentications.size() == 1 ? ServiceTicket.PREFIX : ServiceTicket.PROXY_TICKET_PREFIX;
    final String ticketId = serviceTicketUniqueTicketIdGenerator.getNewTicketId(ticketPrefix);
    final ServiceTicket serviceTicket =
        ticketGrantingTicket.grantServiceTicket(
            ticketId, service, this.serviceTicketExpirationPolicy, currentAuthentication != null);

    this.serviceTicketRegistry.addTicket(serviceTicket);

    logger.info(
        "Granted ticket [{}] for service [{}] for user [{}]",
        serviceTicket.getId(),
        service.getId(),
        principal.getId());

    return serviceTicket;
  }
  @Audit(
      action = "SERVICE_TICKET",
      actionResolverName = "GRANT_SERVICE_TICKET_RESOLVER",
      resourceResolverName = "GRANT_SERVICE_TICKET_RESOURCE_RESOLVER")
  @Profiled(tag = "GRANT_SERVICE_TICKET", logFailuresSeparately = false)
  @Transactional(readOnly = false)
  @Override
  public String grantServiceTicket(
      final String ticketGrantingTicketId, final Service service, final Credential... credentials)
      throws AuthenticationException, TicketException {
    Assert.notNull(ticketGrantingTicketId, "ticketGrantingticketId cannot be null");
    Assert.notNull(service, "service cannot be null");

    final TicketGrantingTicket ticketGrantingTicket =
        this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);

    if (ticketGrantingTicket == null) {
      logger.debug(
          "TicketGrantingTicket [{}] cannot be found in the ticket registry.",
          ticketGrantingTicketId);
      throw new InvalidTicketException(ticketGrantingTicketId);
    }

    synchronized (ticketGrantingTicket) {
      if (ticketGrantingTicket.isExpired()) {
        this.ticketRegistry.deleteTicket(ticketGrantingTicketId);
        logger.debug(
            "TicketGrantingTicket[{}] has expired and is now deleted from the ticket registry.",
            ticketGrantingTicketId);
        throw new InvalidTicketException(ticketGrantingTicketId);
      }
    }

    final RegisteredService registeredService = this.servicesManager.findServiceBy(service);

    verifyRegisteredServiceProperties(registeredService, service);

    if (!registeredService.isSsoEnabled()
        && credentials == null
        && ticketGrantingTicket.getCountOfUses() > 0) {
      logger.warn("ServiceManagement: Service [{}] is not allowed to use SSO.", service.getId());
      throw new UnauthorizedSsoServiceException();
    }

    // CAS-1019
    final List<Authentication> authns = ticketGrantingTicket.getChainedAuthentications();
    if (authns.size() > 1) {
      if (!registeredService.isAllowedToProxy()) {
        final String message =
            String.format(
                "ServiceManagement: Proxy attempt by service [%s] (registered service [%s]) is not allowed.",
                service.getId(), registeredService.toString());
        logger.warn(message);
        throw new UnauthorizedProxyingException(message);
      }
    }

    if (credentials != null) {
      final Authentication current = this.authenticationManager.authenticate(credentials);
      final Authentication original = ticketGrantingTicket.getAuthentication();
      if (!current.getPrincipal().equals(original.getPrincipal())) {
        throw new MixedPrincipalException(current, current.getPrincipal(), original.getPrincipal());
      }
      ticketGrantingTicket.getSupplementalAuthentications().add(current);
    }

    // Perform security policy check by getting the authentication that satisfies the configured
    // policy
    // This throws if no suitable policy is found
    getAuthenticationSatisfiedByPolicy(
        ticketGrantingTicket, new ServiceContext(service, registeredService));

    final String uniqueTicketIdGenKey = service.getClass().getName();
    if (!this.uniqueTicketIdGeneratorsForService.containsKey(uniqueTicketIdGenKey)) {
      logger.warn(
          "Cannot create service ticket because the key [{}] for service [{}] is not linked to a ticket id generator",
          uniqueTicketIdGenKey,
          service.getId());
      throw new UnauthorizedSsoServiceException();
    }

    final UniqueTicketIdGenerator serviceTicketUniqueTicketIdGenerator =
        this.uniqueTicketIdGeneratorsForService.get(uniqueTicketIdGenKey);

    final String generatedServiceTicketId =
        serviceTicketUniqueTicketIdGenerator.getNewTicketId(ServiceTicket.PREFIX);
    logger.debug(
        "Generated service ticket id [{}] for ticket granting ticket [{}]",
        generatedServiceTicketId,
        ticketGrantingTicket.getId());

    final ServiceTicket serviceTicket =
        ticketGrantingTicket.grantServiceTicket(
            generatedServiceTicketId,
            service,
            this.serviceTicketExpirationPolicy,
            credentials != null);

    this.serviceTicketRegistry.addTicket(serviceTicket);

    if (logger.isInfoEnabled()) {
      final List<Authentication> authentications =
          serviceTicket.getGrantingTicket().getChainedAuthentications();
      final String formatString = "Granted %s ticket [%s] for service [%s] for user [%s]";
      final String type;
      final String principalId =
          authentications.get(authentications.size() - 1).getPrincipal().getId();

      if (authentications.size() == 1) {
        type = "service";
      } else {
        type = "proxy";
      }

      logger.info(
          String.format(formatString, type, serviceTicket.getId(), service.getId(), principalId));
    }

    return serviceTicket.getId();
  }