All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.purpledragon.sttp.scribe.ScribeOAuth20Backend.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2018 Michael Stringer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package software.purpledragon.sttp.scribe

import com.github.scribejava.core.exceptions.OAuthException
import com.github.scribejava.core.model.{OAuth2AccessToken, OAuthRequest, Response}
import com.github.scribejava.core.oauth.OAuth20Service
import software.purpledragon.sttp.scribe.QueryParamEncodingStyle.Sttp

object ScribeOAuth20Backend extends Logging {
  private val TokenExpiredHeaderName = "WWW-Authenticate"
  private val TokenExpiredHeaderPattern = "Bearer.*error=\"invalid_token\".*".r

  val DefaultTokenExpiredCheck: TokenExpiredResponseCheck = { response =>
    response.getHeader(TokenExpiredHeaderName) match {
      case TokenExpiredHeaderPattern() =>
        true

      case _ =>
        false
    }
  }
}

class ScribeOAuth20Backend(
    service: OAuth20Service,
    tokenProvider: OAuth2TokenProvider,
    grantType: OAuth2GrantType = OAuth2GrantType.AuthorizationCode,
    isTokenExpiredResponse: TokenExpiredResponseCheck = ScribeOAuth20Backend.DefaultTokenExpiredCheck,
    encodingStyle: QueryParamEncodingStyle = Sttp
) extends ScribeBackend(service, isTokenExpiredResponse, encodingStyle)
    with Logging {

  private var oauthToken: Option[OAuth2AccessToken] = None

  override final def withEncodingStyle(style: QueryParamEncodingStyle): ScribeOAuth20Backend = {
    new ScribeOAuth20Backend(service, tokenProvider, grantType, isTokenExpiredResponse, style)
  }

  override protected def signRequest(request: OAuthRequest): Unit = {
    service.signRequest(currentToken, request)
  }

  override protected def renewAccessToken(response: Response): Boolean = {
    try {
      logger.debug("Renewing access token for request")

      val newToken = grantType match {
        case OAuth2GrantType.AuthorizationCode =>
          service.refreshAccessToken(currentToken.getRefreshToken)
        case OAuth2GrantType.ClientCredentials =>
          service.getAccessTokenClientCredentialsGrant()
      }
      tokenProvider.tokenRenewed(newToken)
      oauthToken = Some(newToken)
      true
    } catch {
      case oae: OAuthException =>
        logger.warn("Error while renewing OAuth token: {}", oae.getMessage)
        logger.trace("Error while renewing OAuth token", oae)
        false
    }
  }

  private def currentToken: OAuth2AccessToken = {
    if (oauthToken.isEmpty) {
      oauthToken = Some(tokenProvider.accessTokenForRequest)
    }

    oauthToken.get
  }
}

trait OAuth2TokenProvider extends OAuthTokenProvider[OAuth2AccessToken]

object OAuth2TokenProvider {

  /**
   * Basic [[OAuth2TokenProvider]] for situations where you don't need to store any renewed tokens. Think *very*
   * carefully before using this token provider!
   *
   * @param token initial access token to use.
   */
  def basicProviderFor(token: OAuth2AccessToken): OAuth2TokenProvider = {
    new OAuth2TokenProvider() {
      private var current: OAuth2AccessToken = token

      override def accessTokenForRequest: OAuth2AccessToken = current
      override def tokenRenewed(newToken: OAuth2AccessToken): Unit = current = newToken
    }
  }
}

sealed trait OAuth2GrantType

object OAuth2GrantType {
  case object AuthorizationCode extends OAuth2GrantType
  case object ClientCredentials extends OAuth2GrantType
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy