@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();
  }
  @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);
      }
    }
  }
  @Override
  protected ModelAndView handleRequestInternal(
      final HttpServletRequest request, final HttpServletResponse response) throws Exception {
    final HttpSession session = request.getSession();

    // get cas login service ticket
    final String serviceTicketId = request.getParameter(OAuthConstants.TICKET);
    LOGGER.debug("{} : {}", OAuthConstants.TICKET, serviceTicketId);

    // first time this url is requested the login ticket will be a query parameter
    if (serviceTicketId != null) {
      // create the login ticket granting ticket
      final ServiceTicket serviceTicket = (ServiceTicket) ticketRegistry.getTicket(serviceTicketId);
      // login service ticket should be valid
      if (serviceTicket == null || serviceTicket.isExpired()) {
        LOGGER.error("Service Ticket expired : {}", serviceTicketId);
        return OAuthUtils.writeTextError(
            response, OAuthConstants.INVALID_GRANT, HttpStatus.SC_BAD_REQUEST);
      }
      final TicketGrantingTicket ticketGrantingTicket = serviceTicket.getGrantingTicket();
      // remove login service ticket
      ticketRegistry.deleteTicket(serviceTicket.getId());

      // store the login tgt id in the user's session, used to create service tickets for validation
      // and
      // oauth credentials later in the flow.
      session.setAttribute(OAuthConstants.OAUTH20_LOGIN_TICKET_ID, ticketGrantingTicket.getId());

      // redirect back to self, clears the service ticket from the url, allows the url to be
      // requested multiple
      // times w/o error
      return OAuthUtils.redirectTo(request.getRequestURL().toString());
    }

    // get cas login service ticket from the session
    String ticketGrantingTicketId =
        (String) session.getAttribute(OAuthConstants.OAUTH20_LOGIN_TICKET_ID);
    LOGGER.debug("{} : {}", OAuthConstants.TICKET, ticketGrantingTicketId);
    if (StringUtils.isBlank(ticketGrantingTicketId)) {
      LOGGER.error("Missing Ticket Granting Ticket");
      return OAuthUtils.writeTextError(
          response, OAuthConstants.INVALID_GRANT, HttpStatus.SC_BAD_REQUEST);
    }

    // verify the login ticket granting ticket is still valid
    TicketGrantingTicket ticketGrantingTicket =
        (TicketGrantingTicket) ticketRegistry.getTicket(ticketGrantingTicketId);
    if (ticketGrantingTicket == null || ticketGrantingTicket.isExpired()) {
      LOGGER.error("Ticket Granting Ticket expired : {}", ticketGrantingTicketId);
      return OAuthUtils.writeTextError(
          response, OAuthConstants.INVALID_GRANT, HttpStatus.SC_BAD_REQUEST);
    }

    String callbackUrl =
        request
            .getRequestURL()
            .toString()
            .replace(
                "/" + OAuthConstants.CALLBACK_AUTHORIZE_URL,
                "/" + OAuthConstants.CALLBACK_AUTHORIZE_ACTION_URL);
    LOGGER.debug("{} : {}", OAuthConstants.CALLBACK_AUTHORIZE_ACTION_URL, callbackUrl);

    String allowCallbackUrl =
        OAuthUtils.addParameter(
            callbackUrl,
            OAuthConstants.OAUTH20_APPROVAL_PROMPT_ACTION,
            OAuthConstants.OAUTH20_APPROVAL_PROMPT_ACTION_ALLOW);

    final Map<String, Object> model = new HashMap<>();
    model.put("callbackUrl", callbackUrl);

    final Boolean bypassApprovalPrompt =
        (Boolean) session.getAttribute(OAuthConstants.BYPASS_APPROVAL_PROMPT);
    LOGGER.debug("bypassApprovalPrompt : {}", bypassApprovalPrompt);
    if (bypassApprovalPrompt != null && bypassApprovalPrompt) {
      return OAuthUtils.redirectTo(allowCallbackUrl);
    }

    final String clientId = (String) session.getAttribute(OAuthConstants.OAUTH20_CLIENT_ID);
    LOGGER.debug("{} : {}", OAuthConstants.CLIENT_ID, clientId);

    final Principal loginPrincipal = ticketGrantingTicket.getAuthentication().getPrincipal();

    final String approvalPrompt =
        (String) session.getAttribute(OAuthConstants.OAUTH20_APPROVAL_PROMPT);
    LOGGER.debug("approvalPrompt : {}", approvalPrompt);
    if (StringUtils.isBlank(approvalPrompt)
        || !approvalPrompt.equalsIgnoreCase(OAuthConstants.APPROVAL_PROMPT_FORCE)) {
      final TicketGrantingTicket refreshToken =
          OAuthTokenUtils.getRefreshToken(centralAuthenticationService, clientId, loginPrincipal);
      if (refreshToken != null) {
        return OAuthUtils.redirectTo(allowCallbackUrl);
      }
    }

    // retrieve service name from session
    final String serviceName = (String) session.getAttribute(OAuthConstants.OAUTH20_SERVICE_NAME);
    LOGGER.debug("serviceName : {}", serviceName);
    model.put("serviceName", serviceName);

    return new ModelAndView(OAuthConstants.CONFIRM_VIEW, model);
  }