@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;
  }
  @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();
  }
  /**
   * 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;
  }
  @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);
      }
    }
  }