/**
   * Issue access token in exchange to an Authorization Grant.
   *
   * @param tokenReqDTO <Code>OAuth2AccessTokenReqDTO</Code> representing the Access Token request
   * @return <Code>OAuth2AccessTokenRespDTO</Code> representing the Access Token response
   */
  public OAuth2AccessTokenRespDTO issueAccessToken(OAuth2AccessTokenReqDTO tokenReqDTO) {

    if (log.isDebugEnabled()) {
      log.debug(
          "Access Token request received for Client ID "
              + tokenReqDTO.getClientId()
              + ", User ID "
              + tokenReqDTO.getResourceOwnerUsername()
              + ", Scope : "
              + Arrays.toString(tokenReqDTO.getScope())
              + " and Grant Type : "
              + tokenReqDTO.getGrantType());
    }

    try {
      AccessTokenIssuer tokenIssuer = AccessTokenIssuer.getInstance();
      return tokenIssuer.issue(tokenReqDTO);
    } catch (InvalidOAuthClientException e) {
      if (log.isDebugEnabled()) {
        log.debug(
            "Error occurred while issuing access token for Client ID : "
                + tokenReqDTO.getClientId()
                + ", User ID: "
                + tokenReqDTO.getResourceOwnerUsername()
                + ", Scope : "
                + Arrays.toString(tokenReqDTO.getScope())
                + " and Grant Type : "
                + tokenReqDTO.getGrantType(),
            e);
      }
      OAuth2AccessTokenRespDTO tokenRespDTO = new OAuth2AccessTokenRespDTO();
      tokenRespDTO.setError(true);
      tokenRespDTO.setErrorCode(OAuth2ErrorCodes.INVALID_CLIENT);
      tokenRespDTO.setErrorMsg("Invalid Client");
      return tokenRespDTO;
    } catch (Exception e) { // in case of an error, consider it as a system error
      log.error(
          "Error occurred while issuing the access token for Client ID : "
              + tokenReqDTO.getClientId()
              + ", User ID "
              + tokenReqDTO.getResourceOwnerUsername()
              + ", Scope : "
              + Arrays.toString(tokenReqDTO.getScope())
              + " and Grant Type : "
              + tokenReqDTO.getGrantType(),
          e);
      OAuth2AccessTokenRespDTO tokenRespDTO = new OAuth2AccessTokenRespDTO();
      tokenRespDTO.setError(true);
      if (e.getCause().getCause() instanceof SQLIntegrityConstraintViolationException) {
        tokenRespDTO.setErrorCode("sql_error");
      } else {
        tokenRespDTO.setErrorCode(OAuth2ErrorCodes.SERVER_ERROR);
      }
      tokenRespDTO.setErrorMsg("Server Error");
      return tokenRespDTO;
    }
  }
 private void setResponseHeaders(
     OAuthTokenReqMessageContext tokReqMsgCtx, OAuth2AccessTokenRespDTO tokenRespDTO) {
   if (tokReqMsgCtx.getProperty("RESPONSE_HEADERS") != null) {
     tokenRespDTO.setResponseHeaders(
         (ResponseHeader[]) tokReqMsgCtx.getProperty("RESPONSE_HEADERS"));
   }
 }
  private void addUserAttributesToCache(
      OAuth2AccessTokenReqDTO tokenReqDTO, OAuth2AccessTokenRespDTO tokenRespDTO) {
    AuthorizationGrantCacheKey oldCacheKey =
        new AuthorizationGrantCacheKey(tokenReqDTO.getAuthorizationCode());
    // checking getUserAttributesId vale of cacheKey before retrieve entry from cache as it causes
    // to NPE
    if (oldCacheKey.getUserAttributesId() != null) {
      AuthorizationGrantCacheEntry authorizationGrantCacheEntry =
          AuthorizationGrantCache.getInstance().getValueFromCacheByCode(oldCacheKey);
      AuthorizationGrantCacheKey newCacheKey =
          new AuthorizationGrantCacheKey(tokenRespDTO.getAccessToken());

      if (AuthorizationGrantCache.getInstance().getValueFromCacheByToken(newCacheKey) == null) {
        if (log.isDebugEnabled()) {
          log.debug(
              "No AuthorizationGrantCache entry found for the access token:"
                  + newCacheKey.getUserAttributesId()
                  + ", hence adding to cache");
        }
        AuthorizationGrantCache.getInstance()
            .addToCacheByToken(newCacheKey, authorizationGrantCacheEntry);
        AuthorizationGrantCache.getInstance().clearCacheEntryByCode(oldCacheKey);
      } else {
        // if the user attributes are already saved for access token, no need to add again.
      }
    }
  }
 private OAuth2AccessTokenRespDTO handleError(
     String errorCode, String errorMsg, OAuth2AccessTokenReqDTO tokenReqDTO) {
   if (log.isDebugEnabled()) {
     log.debug(
         "OAuth-Error-Code="
             + errorCode
             + " client-id="
             + tokenReqDTO.getClientId()
             + " grant-type="
             + tokenReqDTO.getGrantType()
             + " scope="
             + OAuth2Util.buildScopeString(tokenReqDTO.getScope()));
   }
   OAuth2AccessTokenRespDTO tokenRespDTO;
   tokenRespDTO = new OAuth2AccessTokenRespDTO();
   tokenRespDTO.setError(true);
   tokenRespDTO.setErrorCode(errorCode);
   tokenRespDTO.setErrorMsg(errorMsg);
   return tokenRespDTO;
 }
  @POST
  @Path("/")
  @Consumes("application/x-www-form-urlencoded")
  @Produces("application/json")
  public Response issueAccessToken(
      @Context HttpServletRequest request, MultivaluedMap<String, String> paramMap)
      throws OAuthSystemException {

    try {
      PrivilegedCarbonContext.startTenantFlow();
      PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
      carbonContext.setTenantId(MultitenantConstants.SUPER_TENANT_ID);
      carbonContext.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);

      HttpServletRequestWrapper httpRequest = new OAuthRequestWrapper(request, paramMap);

      if (log.isDebugEnabled()) {
        logAccessTokenRequest(httpRequest);
      }

      // extract the basic auth credentials if present in the request and use for
      // authentication.
      if (request.getHeader(OAuthConstants.HTTP_REQ_HEADER_AUTHZ) != null) {

        try {
          String[] clientCredentials =
              EndpointUtil.extractCredentialsFromAuthzHeader(
                  request.getHeader(OAuthConstants.HTTP_REQ_HEADER_AUTHZ));

          // The client MUST NOT use more than one authentication method in each request
          if (paramMap.containsKey(OAuth.OAUTH_CLIENT_ID)
              && paramMap.containsKey(OAuth.OAUTH_CLIENT_SECRET)) {
            return handleBasicAuthFailure();
          }

          // If a client sends an invalid base64 encoded clientid:clientsecret value, it results in
          // this
          // array to only contain 1 element. This happens on specific errors though.
          if (clientCredentials.length != 2) {
            return handleBasicAuthFailure();
          }

          // add the credentials available in Authorization header to the parameter map
          paramMap.add(OAuth.OAUTH_CLIENT_ID, clientCredentials[0]);
          paramMap.add(OAuth.OAUTH_CLIENT_SECRET, clientCredentials[1]);

        } catch (OAuthClientException e) {
          // malformed credential string is considered as an auth failure.
          log.error("Error while extracting credentials from authorization header", e);
          return handleBasicAuthFailure();
        }
      }

      try {
        CarbonOAuthTokenRequest oauthRequest = new CarbonOAuthTokenRequest(httpRequest);
        // exchange the access token for the authorization grant.
        OAuth2AccessTokenRespDTO oauth2AccessTokenResp = getAccessToken(oauthRequest);
        // if there BE has returned an error
        if (oauth2AccessTokenResp.getErrorMsg() != null) {
          // if there is an auth failure, HTTP 401 Status Code should be sent back to the client.
          if (OAuth2ErrorCodes.INVALID_CLIENT.equals(oauth2AccessTokenResp.getErrorCode())) {
            return handleBasicAuthFailure();
          } else if ("sql_error".equals(oauth2AccessTokenResp.getErrorCode())) {
            return handleSQLError();
          } else if (OAuth2ErrorCodes.SERVER_ERROR.equals(oauth2AccessTokenResp.getErrorCode())) {
            return handleServerError();
          } else {
            // Otherwise send back HTTP 400 Status Code
            OAuthResponse.OAuthErrorResponseBuilder oAuthErrorResponseBuilder =
                OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                    .setError(oauth2AccessTokenResp.getErrorCode())
                    .setErrorDescription(oauth2AccessTokenResp.getErrorMsg());
            OAuthResponse response = oAuthErrorResponseBuilder.buildJSONMessage();

            ResponseHeader[] headers = oauth2AccessTokenResp.getResponseHeaders();
            ResponseBuilder respBuilder = Response.status(response.getResponseStatus());

            if (headers != null && headers.length > 0) {
              for (int i = 0; i < headers.length; i++) {
                if (headers[i] != null) {
                  respBuilder.header(headers[i].getKey(), headers[i].getValue());
                }
              }
            }

            return respBuilder.entity(response.getBody()).build();
          }
        } else {
          OAuthTokenResponseBuilder oAuthRespBuilder =
              OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK)
                  .setAccessToken(oauth2AccessTokenResp.getAccessToken())
                  .setRefreshToken(oauth2AccessTokenResp.getRefreshToken())
                  .setExpiresIn(Long.toString(oauth2AccessTokenResp.getExpiresIn()))
                  .setTokenType(BEARER);
          oAuthRespBuilder.setScope(oauth2AccessTokenResp.getAuthorizedScopes());

          // OpenID Connect ID token
          if (oauth2AccessTokenResp.getIDToken() != null) {
            oAuthRespBuilder.setParam(OAuthConstants.ID_TOKEN, oauth2AccessTokenResp.getIDToken());
          }
          OAuthResponse response = oAuthRespBuilder.buildJSONMessage();
          ResponseHeader[] headers = oauth2AccessTokenResp.getResponseHeaders();
          ResponseBuilder respBuilder =
              Response.status(response.getResponseStatus())
                  .header(
                      OAuthConstants.HTTP_RESP_HEADER_CACHE_CONTROL,
                      OAuthConstants.HTTP_RESP_HEADER_VAL_CACHE_CONTROL_NO_STORE)
                  .header(
                      OAuthConstants.HTTP_RESP_HEADER_PRAGMA,
                      OAuthConstants.HTTP_RESP_HEADER_VAL_PRAGMA_NO_CACHE);

          if (headers != null && headers.length > 0) {
            for (int i = 0; i < headers.length; i++) {
              if (headers[i] != null) {
                respBuilder.header(headers[i].getKey(), headers[i].getValue());
              }
            }
          }

          return respBuilder.entity(response.getBody()).build();
        }

      } catch (OAuthProblemException e) {
        log.error("Error while creating the Carbon OAuth token request", e);
        OAuthResponse res =
            OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                .error(e)
                .buildJSONMessage();
        return Response.status(res.getResponseStatus()).entity(res.getBody()).build();
      }

    } finally {
      PrivilegedCarbonContext.endTenantFlow();
    }
  }
  public OAuth2AccessTokenRespDTO issue(OAuth2AccessTokenReqDTO tokenReqDTO)
      throws IdentityException, InvalidOAuthClientException {

    String grantType = tokenReqDTO.getGrantType();
    OAuth2AccessTokenRespDTO tokenRespDTO;

    AuthorizationGrantHandler authzGrantHandler = authzGrantHandlers.get(grantType);

    OAuthTokenReqMessageContext tokReqMsgCtx = new OAuthTokenReqMessageContext(tokenReqDTO);

    // If multiple client authentication methods have been used the authorization server must reject
    // the request
    int authenticatorHandlerIndex = -1;
    for (int i = 0; i < clientAuthenticationHandlers.size(); i++) {
      if (clientAuthenticationHandlers.get(i).canAuthenticate(tokReqMsgCtx)) {
        if (authenticatorHandlerIndex > -1) {
          log.debug(
              "Multiple Client Authentication Methods used for client id : "
                  + tokenReqDTO.getClientId());
          tokenRespDTO =
              handleError(
                  OAuthConstants.OAuthError.TokenResponse.UNSUPPORTED_CLIENT_AUTHENTICATION_METHOD,
                  "Unsupported Client Authentication Method!",
                  tokenReqDTO);
          setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
          return tokenRespDTO;
        }
        authenticatorHandlerIndex = i;
      }
    }
    if (authenticatorHandlerIndex < 0 && authzGrantHandler.isConfidentialClient()) {
      log.debug(
          "Confidential client cannot be authenticated for client id : "
              + tokenReqDTO.getClientId());
      tokenRespDTO =
          handleError(
              OAuthConstants.OAuthError.TokenResponse.UNSUPPORTED_CLIENT_AUTHENTICATION_METHOD,
              "Unsupported Client Authentication Method!",
              tokenReqDTO);
      setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
      return tokenRespDTO;
    }

    ClientAuthenticationHandler clientAuthHandler = null;
    if (authenticatorHandlerIndex > -1) {
      clientAuthHandler = clientAuthenticationHandlers.get(authenticatorHandlerIndex);
    }
    boolean isAuthenticated;
    if (clientAuthHandler != null) {
      isAuthenticated = clientAuthHandler.authenticateClient(tokReqMsgCtx);
    } else {
      isAuthenticated = true;
    }
    if (!isAuthenticated) {
      if (log.isDebugEnabled()) {
        log.debug("Client Authentication failed for client Id: " + tokenReqDTO.getClientId());
      }
      tokenRespDTO =
          handleError(
              OAuthError.TokenResponse.INVALID_CLIENT,
              "Client credentials are invalid.",
              tokenReqDTO);
      setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
      return tokenRespDTO;
    }

    // loading the stored application data
    OAuthAppDO oAuthAppDO = getAppInformation(tokenReqDTO);
    if (!authzGrantHandler.isOfTypeApplicationUser()) {
      tokReqMsgCtx.setAuthorizedUser(OAuth2Util.getUserFromUserName(oAuthAppDO.getUserName()));
      tokReqMsgCtx
          .getAuthorizedUser()
          .setTenantDomain(IdentityTenantUtil.getTenantDomain(oAuthAppDO.getTenantId()));
    }

    boolean isValidGrant = false;
    String error = "Provided Authorization Grant is invalid";
    try {
      isValidGrant = authzGrantHandler.validateGrant(tokReqMsgCtx);
    } catch (IdentityOAuth2Exception e) {
      if (log.isDebugEnabled()) {
        log.debug("Error occurred while validating grant", e);
      }
      error = e.getMessage();
    }

    if (!isValidGrant) {
      if (log.isDebugEnabled()) {
        log.debug("Invalid Grant provided by the client Id: " + tokenReqDTO.getClientId());
      }
      tokenRespDTO = handleError(OAuthError.TokenResponse.INVALID_GRANT, error, tokenReqDTO);
      setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
      return tokenRespDTO;
    }

    boolean isAuthorized = authzGrantHandler.authorizeAccessDelegation(tokReqMsgCtx);
    if (!isAuthorized) {
      if (log.isDebugEnabled()) {
        log.debug("Invalid authorization for client Id = " + tokenReqDTO.getClientId());
      }
      tokenRespDTO =
          handleError(
              OAuthError.TokenResponse.UNAUTHORIZED_CLIENT, "Unauthorized Client!", tokenReqDTO);
      setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
      return tokenRespDTO;
    }

    boolean isValidScope = authzGrantHandler.validateScope(tokReqMsgCtx);
    if (!isValidScope) {
      if (log.isDebugEnabled()) {
        log.debug("Invalid scope provided by client Id: " + tokenReqDTO.getClientId());
      }
      tokenRespDTO =
          handleError(OAuthError.TokenResponse.INVALID_SCOPE, "Invalid Scope!", tokenReqDTO);
      setResponseHeaders(tokReqMsgCtx, tokenRespDTO);
      return tokenRespDTO;
    }

    try {
      // set the token request context to be used by downstream handlers. This is introduced as a
      // fix for
      // IDENTITY-4111.
      OAuth2Util.setTokenRequestContext(tokReqMsgCtx);
      tokenRespDTO = authzGrantHandler.issue(tokReqMsgCtx);
    } finally {
      // clears the token request context.
      OAuth2Util.clearTokenRequestContext();
    }

    tokenRespDTO.setCallbackURI(oAuthAppDO.getCallbackUrl());

    String[] scopes = tokReqMsgCtx.getScope();
    if (scopes != null && scopes.length > 0) {
      StringBuilder scopeString = new StringBuilder("");
      for (String scope : scopes) {
        scopeString.append(scope);
        scopeString.append(" ");
      }
      tokenRespDTO.setAuthorizedScopes(scopeString.toString().trim());
    }

    setResponseHeaders(tokReqMsgCtx, tokenRespDTO);

    // Do not change this log format as these logs use by external applications
    if (log.isDebugEnabled()) {
      log.debug(
          "Access token issued to client Id: "
              + tokenReqDTO.getClientId()
              + " username: "******" and scopes: "
              + tokenRespDTO.getAuthorizedScopes());
    }

    if (tokReqMsgCtx.getScope() != null && OAuth2Util.isOIDCAuthzRequest(tokReqMsgCtx.getScope())) {
      IDTokenBuilder builder =
          OAuthServerConfiguration.getInstance().getOpenIDConnectIDTokenBuilder();
      tokenRespDTO.setIDToken(builder.buildIDToken(tokReqMsgCtx, tokenRespDTO));
    }

    if (tokenReqDTO.getGrantType().equals(GrantType.AUTHORIZATION_CODE.toString())) {
      addUserAttributesToCache(tokenReqDTO, tokenRespDTO);
    }

    return tokenRespDTO;
  }