diff --git a/library/build.gradle b/library/build.gradle index 7b6cfc9..912de58 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,10 +1,17 @@ +apply plugin: 'kotlin' apply plugin: 'groovy' apply plugin: 'maven' buildscript { + ext.kotlin_version = '1.3.41' + repositories { mavenCentral() } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } } jar.archiveName = "Heimdall.Droid.jar" @@ -17,17 +24,34 @@ repositories { } dependencies { + // Kotlin + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" // Rx - implementation 'io.reactivex.rxjava2:rxjava:2.2.7' + implementation 'io.reactivex.rxjava2:rxjava:2.2.8' // GSON implementation 'com.google.code.gson:gson:2.8.5' // Testing - testImplementation "cglib:cglib:2.2" testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:2.27.0' + testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0" + + testImplementation "cglib:cglib:2.2" testImplementation('org.spockframework:spock-core:1.0-groovy-2.4') { exclude group: 'junit' } } + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} + +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessToken.java b/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessToken.java deleted file mode 100644 index cfa9ec2..0000000 --- a/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessToken.java +++ /dev/null @@ -1,90 +0,0 @@ -package de.rheinfabrik.heimdall2; - -import com.google.gson.annotations.SerializedName; -import java.io.Serializable; -import java.util.Calendar; - -/** - * OAuth2AccessToken represents an OAuth2AccessToken response as described in https://tools.ietf.org/html/rfc6749#section-5.1. - */ -public class OAuth2AccessToken implements Serializable { - - // Properties - - /** - * REQUIRED - * The type of the token issued as described in https://tools.ietf.org/html/rfc6749#section-7.1. - * Value is case insensitive. - */ - @SerializedName("token_type") - public String tokenType; - - /** - * REQUIRED - * The access token issued by the authorization server. - */ - @SerializedName("access_token") - public String accessToken; - - /** - * OPTIONAL - * The refresh token, which can be used to obtain new - * access tokens using the same authorization grant as described - * in https://tools.ietf.org/html/rfc6749#section-6. - */ - @SerializedName("refresh_token") - public String refreshToken; - - /** - * RECOMMENDED - * The lifetime in seconds of the access token. For - * example, the value "3600" denotes that the access token will - * expire in one hour from the time the response was generated. - * If omitted, the authorization server SHOULD provide the - * expiration time via other means or document the default value. - */ - @SerializedName("expires_in") - public Integer expiresIn; - - /** - * The expiration date used by Heimdall. - */ - @SerializedName("heimdall_expiration_date") - public Calendar expirationDate; - - // Public Api - - /** - * This method returns whether the access token is expired or not. - * - * @return True if expired. Otherwise false. - */ - public boolean isExpired() { - return expirationDate != null && Calendar.getInstance().after(expirationDate); - } - - // Object - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - - if (!(other instanceof OAuth2AccessToken)) { - return false; - } - - OAuth2AccessToken otherToken = (OAuth2AccessToken) other; - - return accessToken.equals(otherToken.accessToken) && tokenType.equals(otherToken.tokenType); - } - - @Override - public int hashCode() { - int result = tokenType.hashCode(); - result = 31 * result + accessToken.hashCode(); - - return result; - } -} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessToken.kt b/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessToken.kt new file mode 100644 index 0000000..4a8c8f3 --- /dev/null +++ b/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessToken.kt @@ -0,0 +1,76 @@ +package de.rheinfabrik.heimdall2 + +import com.google.gson.annotations.SerializedName +import java.io.Serializable +import java.util.Calendar + +class OAuth2AccessToken( + /** + * REQUIRED + * The type of the token issued as described in https://tools.ietf.org/html/rfc6749#section-7.1. + * Value is case insensitive. + */ + @SerializedName("token_type") + var tokenType: String? = null, + + /** + * REQUIRED + * The access token issued by the authorization server. + */ + @SerializedName("access_token") + var accessToken: String? = null, + + /** + * OPTIONAL + * The refresh token, which can be used to obtain new + * access tokens using the same authorization grant as described + * in https://tools.ietf.org/html/rfc6749#section-6. + */ + @SerializedName("refresh_token") + var refreshToken: String? = null, + + /** + * RECOMMENDED + * The lifetime in seconds of the access token. For + * example, the value "3600" denotes that the access token will + * expire in one hour from the time the response was generated. + * If omitted, the authorization server SHOULD provide the + * expiration time via other means or document the default value. + */ + @SerializedName("expires_in") + var expiresIn: Int? = null, + + /** + * The expiration date used by Heimdall. + */ + @SerializedName("heimdall_expiration_date") + var expirationDate: Calendar? = null +) : Serializable { + + // Public API + + /** + * Returns whether the access token expired or not. + * + * @return True if expired. Otherwise false. + */ + fun isExpired(): Boolean = + expirationDate != null && + Calendar.getInstance().after(expirationDate) + + + override fun equals(other: Any?): Boolean = + when { + this === other -> true + other !is OAuth2AccessToken -> false + else -> { + accessToken.equals(other.accessToken) && tokenType.equals(other.accessToken) + } + } + + + override fun hashCode(): Int = + tokenType.hashCode().let { + 31 * it + accessToken.hashCode() + } +} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManager.java b/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManager.java deleted file mode 100644 index 144da0a..0000000 --- a/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManager.java +++ /dev/null @@ -1,107 +0,0 @@ -package de.rheinfabrik.heimdall2; - -import de.rheinfabrik.heimdall2.grants.OAuth2Grant; -import de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant; -import io.reactivex.Single; -import java.util.Calendar; - -/** - * The all-seeing and all-hearing guardian sentry of your application who - * stands on the rainbow bridge to handle all your access tokens needs! - * - * @param The token type. - */ -public class OAuth2AccessTokenManager { - - // Members - - private final OAuth2AccessTokenStorage mStorage; - - // Constructor - - /** - * The designated constructor. - * - * @param storage The OAuth2AccessTokenStorage used to store and retrieve the access token. - */ - public OAuth2AccessTokenManager(OAuth2AccessTokenStorage storage) { - super(); - - if (storage == null) { - throw new IllegalArgumentException("Storage MUST NOT be null."); - } - - mStorage = storage; - } - - // Public API - - /** - * Returns the underlying storage. - * - * @return - An OAuth2AccessTokenStorage. - */ - public OAuth2AccessTokenStorage getStorage() { - return mStorage; - } - - /** - * Grants a new access token using the given OAuth2 grant. - * - * @param grant A class implementing the OAuth2Grant interface. - * @return - An observable emitting the granted access token. - */ - public Single grantNewAccessToken(OAuth2Grant grant) { - return grantNewAccessToken(grant, Calendar.getInstance()); - } - - /** - * Grants a new access token using the given OAuth2 grant. - * - * @param grant A class implementing the OAuth2Grant interface. - * @param calendar A calendar instance used to calculate the expiration date of the token. - * @return - An observable emitting the granted access token. - */ - public Single grantNewAccessToken(OAuth2Grant grant, Calendar calendar) { - if (grant == null) { - throw new IllegalArgumentException("Grant MUST NOT be null."); - } - - return grant.grantNewAccessToken() - .doOnSuccess(accessToken -> { - if (accessToken.expiresIn != null) { - Calendar expirationDate = (Calendar) calendar.clone(); - expirationDate.add(Calendar.SECOND, accessToken.expiresIn); - accessToken.expirationDate = expirationDate; - } - mStorage.storeAccessToken(accessToken); - }).cache(); - } - - /** - * Returns an Observable emitting an unexpired access token. - * NOTE: In order to work, Heimdall needs an access token which has a refresh_token and an - * expires_in field. - * - * @param refreshAccessTokenGrant The refresh grant that will be used if the access token is expired. - * @return - An Observable emitting an unexpired access token. - */ - public Single getValidAccessToken(final OAuth2RefreshAccessTokenGrant refreshAccessTokenGrant) { - if (refreshAccessTokenGrant == null) { - throw new IllegalArgumentException("RefreshAccessTokenGrant MUST NOT be null."); - } - - return mStorage.getStoredAccessToken() - .flatMap(accessToken -> { - if (accessToken == null) { - return Single.error(new IllegalStateException("No access token found.")); - } else if (accessToken.isExpired()) { - refreshAccessTokenGrant.refreshToken = accessToken.refreshToken; - - return grantNewAccessToken(refreshAccessTokenGrant); - } else { - return Single.just(accessToken); - } - }); - } -} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManager.kt b/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManager.kt new file mode 100644 index 0000000..fb8ea20 --- /dev/null +++ b/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManager.kt @@ -0,0 +1,60 @@ +package de.rheinfabrik.heimdall2 + +import de.rheinfabrik.heimdall2.grants.OAuth2Grant +import de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant +import io.reactivex.Single +import java.util.Calendar + +open class OAuth2AccessTokenManager( + private val mStorage: OAuth2AccessTokenStorage +) { + + // Public API + + /** + * Returns the underlying storage. + * + * @return - An OAuth2AccessTokenStorage. + */ + fun getStorage(): OAuth2AccessTokenStorage = mStorage + + /** + * Grants a new access token using the given OAuth2 grant. + * + * @param grant A class implementing the OAuth2Grant interface. + * @return - An Single emitting the granted access token. + */ + fun grantNewAccessToken( + grant: OAuth2Grant, + calendar: Calendar = Calendar.getInstance() + ): Single = + grant.grantNewAccessToken() + .doOnSuccess { token -> + token.expiresIn?.let { + val newExpirationDate = (calendar.clone() as Calendar).apply { + add(Calendar.SECOND, it) + } + token.expirationDate = newExpirationDate + } + mStorage.storeAccessToken(token) + }.cache() + + /** + * Returns an Observable emitting an unexpired access token. + * NOTE: In order to work, Heimdall needs an access token which has a refresh_token and an + * expires_in field. + * + * @param refreshAccessTokenGrant The refresh grant that will be used if the access token is expired. + * @return - An Single emitting an unexpired access token. + */ + fun getValidAccessToken(refreshAccessTokenGrant: OAuth2RefreshAccessTokenGrant): Single = + mStorage.getStoredAccessToken() + .flatMap { accessToken -> + if (accessToken.isExpired()) { + refreshAccessTokenGrant.refreshToken = accessToken.refreshToken + grantNewAccessToken(refreshAccessTokenGrant) + } else { + Single.just(accessToken) + } + } +} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenStorage.java b/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenStorage.kt similarity index 67% rename from library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenStorage.java rename to library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenStorage.kt index 670d149..64e6eca 100644 --- a/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenStorage.java +++ b/library/src/main/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenStorage.kt @@ -1,15 +1,13 @@ -package de.rheinfabrik.heimdall2; +package de.rheinfabrik.heimdall2 - -import io.reactivex.Single; +import io.reactivex.Single /** * Interface used to define how to store and retrieve a stored access token. * * @param The access token type. */ -public interface OAuth2AccessTokenStorage { - +interface OAuth2AccessTokenStorage { // Public API /** @@ -17,14 +15,14 @@ public interface OAuth2AccessTokenStorage getStoredAccessToken(); + fun getStoredAccessToken(): Single /** * Stores the given access token. * * @param token The access token which will be stored. */ - void storeAccessToken(TAccessToken token); + fun storeAccessToken(token: OAuth2AccessToken) /** * Checks whether there is or is not an access token @@ -32,10 +30,10 @@ public interface OAuth2AccessTokenStorage hasAccessToken(); + fun hasAccessToken(): Single /** * Removes the stored access token. */ - void removeAccessToken(); + fun removeAccessToken() } diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrant.java b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrant.java deleted file mode 100644 index 0df0196..0000000 --- a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrant.java +++ /dev/null @@ -1,138 +0,0 @@ -package de.rheinfabrik.heimdall2.grants; - -import de.rheinfabrik.heimdall2.OAuth2AccessToken; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.reactivex.subjects.BehaviorSubject; -import io.reactivex.subjects.PublishSubject; -import java.net.URL; -import java.net.URLDecoder; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** - * Class representing the Authorization Code Grant as described in https://tools.ietf.org/html/rfc6749#section-4.1. - * - * @param The access token type. - */ -public abstract class OAuth2AuthorizationCodeGrant implements OAuth2Grant { - - // Constants - - /** - * REQUIRED - * The "response_type" which MUST be "code". - */ - public final static String RESPONSE_TYPE = "code"; - - /** - * REQUIRED - * The "grant_type" which MUST be "authorization_code". - */ - public final static String GRANT_TYPE = "authorization_code"; - - // Properties - - /** - * REQUIRED - * The client identifier as described in https://tools.ietf.org/html/rfc6749#section-2.2. - */ - public String clientId; - - /** - * OPTIONAL - * As described in https://tools.ietf.org/html/rfc6749#section-3.1.2. - */ - public String redirectUri; - - /** - * OPTIONAL - * The scope of the access request as described in https://tools.ietf.org/html/rfc6749#section-3.3. - */ - public String scope; - - /** - * RECOMMENDED - * An opaque value used by the client to maintain - * state between the request and callback. The authorization - * server includes this value when redirecting the user-agent back - * to the client. The parameter SHOULD be used for preventing - * cross-site request forgery as described in https://tools.ietf.org/html/rfc6749#section-10.12. - */ - public String state; - - // Public Api - - /** - * Observable emitting the authorization Uri. - */ - public final Observable authorizationUri() { - return mAuthorizationUrlSubject; - } - - /** - * Command you should send a value to whenever an url in e.g. your web view has been loaded. - */ - public final PublishSubject onUrlLoadedCommand = PublishSubject.create(); - - // Abstract Api - - /** - * Called when the grant needs the authorization url. - */ - public abstract URL buildAuthorizationUrl(); - - /** - * Called when the grant was able to grab the code and it wants to exchange it for an access token. - */ - public abstract Observable exchangeTokenUsingCode(String code); - - // Members - - private final BehaviorSubject mAuthorizationUrlSubject = BehaviorSubject.create(); - - // OAuth2AccessToken - - @Override - public Single grantNewAccessToken() { - mAuthorizationUrlSubject.onNext(buildAuthorizationUrl()); - - return onUrlLoadedCommand - .map(uri -> { - List values = getQueryParameters(uri).get(RESPONSE_TYPE); - if (values != null && values.size() >= 1) { - return values.get(0); - } - - return null; - }) - .filter(code -> code != null) - .take(1) - .retry() - .concatMap(this::exchangeTokenUsingCode) - .singleOrError(); - } - - // Private - - private static Map> getQueryParameters(URL url) { - final Map> query_pairs = new LinkedHashMap<>(); - final String[] pairs = url.getQuery().split("&"); - for (String pair : pairs) { - final int idx = pair.indexOf("="); - - try { - final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair; - if (!query_pairs.containsKey(key)) { - query_pairs.put(key, new LinkedList<>()); - } - final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null; - query_pairs.get(key).add(value); - } catch (Exception ignored) {} - } - - return query_pairs; - } -} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrant.kt b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrant.kt new file mode 100644 index 0000000..61bb15f --- /dev/null +++ b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrant.kt @@ -0,0 +1,130 @@ +package de.rheinfabrik.heimdall2.grants + +import de.rheinfabrik.heimdall2.OAuth2AccessToken +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.subjects.BehaviorSubject +import io.reactivex.subjects.PublishSubject +import java.net.URL +import java.net.URLDecoder + +/** + * Class representing the Authorization Code Grant as described in https://tools.ietf.org/html/rfc6749#section-4.1. + * + * @param The access token type. + */ +abstract class OAuth2AuthorizationCodeGrant( + /** + * REQUIRED + * The client identifier as described in https://tools.ietf.org/html/rfc6749#section-2.2. + */ + var clientId: String = "", + + /** + * OPTIONAL + * As described in https://tools.ietf.org/html/rfc6749#section-3.1.2. + */ + var redirectUri: String? = null, + + /** + * OPTIONAL + * The scope of the access request as described in https://tools.ietf.org/html/rfc6749#section-3.3. + */ + var scope: String? = null, + + /** + * RECOMMENDED + * An opaque value used by the client to maintain + * state between the request and callback. The authorization + * server includes this value when redirecting the user-agent back + * to the client. The parameter SHOULD be used for preventing + * cross-site request forgery as described in https://tools.ietf.org/html/rfc6749#section-10.12. + */ + var state: String? = null +) : OAuth2Grant { + + // Constants + companion object { + @JvmStatic + val RESPONSE_TYPE = "code" + @JvmStatic + val GRANT_TYPE = "authorization_code" + private const val UTF_8 = "UTF-8" + } + + // Public Members + /** + * Command you should send a value to whenever an url in e.g. your web view has been loaded. + */ + val onUrlLoadedCommand = PublishSubject.create() + + // Private Members + private val mAuthorizationUrlSubject = BehaviorSubject.create() + + // Abstract API + + /** + * Called when the grant needs the authorization url. + */ + abstract fun buildAuthorizationUrl(): URL + + /** + * Called when the grant was able to grab the code and it wants to exchange for an access token. + */ + abstract fun exchangeTokenUsingCode(code: String): Observable + + // Public API + + /** + * Observable emitting the authorization Uri. + */ + fun authorizationUri() = mAuthorizationUrlSubject + + override fun grantNewAccessToken(): Single { + mAuthorizationUrlSubject.onNext(buildAuthorizationUrl()) + + return onUrlLoadedCommand.map { + getQueryParameters(it)[RESPONSE_TYPE]?.get(0) + }.filter { + it.isNotBlank() + }.take(1) + .retry() + .concatMap { + exchangeTokenUsingCode(it) + } + .singleOrError() + } + + // Private API + + private fun getQueryParameters(url: URL): LinkedHashMap> { + val queryParams = linkedMapOf>() + url.query.split("&").forEach { + val idx = it.indexOf("=") + try { + val key = if (idx > 0) { + URLDecoder.decode( + it.substring(0, idx), + UTF_8 + ) + } else { + it + } + if (!queryParams.containsKey(key)) { + queryParams[key] = mutableListOf() + } + val value = if (idx > 0 && it.length > idx + 1) { + URLDecoder.decode( + it.substring(idx + 1), + UTF_8 + ) + } else "" + + queryParams[key]?.add(value) + } catch (e: Exception) { + // Do nothing + } + } + return queryParams + } +} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrant.java b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrant.java deleted file mode 100644 index d0a0a6c..0000000 --- a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrant.java +++ /dev/null @@ -1,27 +0,0 @@ -package de.rheinfabrik.heimdall2.grants; - -import de.rheinfabrik.heimdall2.OAuth2AccessToken; - -/** - * Class representing the Client Credentials Grant as described in https://tools.ietf.org/html/rfc6749#section-4.4. - * - * @param The access token type. - */ -public abstract class OAuth2ClientCredentialsGrant implements OAuth2Grant { - - // Constants - - /** - * REQUIRED - * The OAuth2 "grant_type". - */ - public static final String GRANT_TYPE = "client_credentials"; - - // Properties - - /** - * OPTIONAL - * The scope of the access request as described in https://tools.ietf.org/html/rfc6749#section-3.3. - */ - public String scope; -} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrant.kt b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrant.kt new file mode 100644 index 0000000..9eec57f --- /dev/null +++ b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrant.kt @@ -0,0 +1,22 @@ +package de.rheinfabrik.heimdall2.grants + +abstract class OAuth2ClientCredentialsGrant( + /** + * OPTIONAL + * The scope of the access request as described in https://tools.ietf.org/html/rfc6749#section-3.3. + */ + val scope: String? = null +) : + OAuth2Grant { + + // Constants + + companion object { + /** + * REQUIRED + * The OAuth2 "grant_type". + */ + @JvmStatic + val GRANT_TYPE = "client_credentials" + } +} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2Grant.java b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2Grant.kt similarity index 58% rename from library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2Grant.java rename to library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2Grant.kt index 62a3a7b..16d610b 100644 --- a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2Grant.java +++ b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2Grant.kt @@ -1,14 +1,14 @@ -package de.rheinfabrik.heimdall2.grants; +package de.rheinfabrik.heimdall2.grants -import de.rheinfabrik.heimdall2.OAuth2AccessToken; -import io.reactivex.Single; +import de.rheinfabrik.heimdall2.OAuth2AccessToken +import io.reactivex.Single /** * Interface describing an OAuth2 Grant as described in https://tools.ietf.org/html/rfc6749#page-23. * * @param The access token type. */ -public interface OAuth2Grant { +interface OAuth2Grant { // Abstract Api @@ -17,5 +17,5 @@ public interface OAuth2Grant { * * @return - An Observable emitting the granted access token. */ - Single grantNewAccessToken(); + fun grantNewAccessToken(): Single } diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrant.java b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrant.kt similarity index 72% rename from library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrant.java rename to library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrant.kt index b3fc6b4..b318062 100644 --- a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrant.java +++ b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrant.kt @@ -1,41 +1,29 @@ -package de.rheinfabrik.heimdall2.grants; - -import de.rheinfabrik.heimdall2.OAuth2AccessToken; +package de.rheinfabrik.heimdall2.grants /** * Class representing the Implicit Grant as described in https://tools.ietf.org/html/rfc6749#section-4.2. * * @param The access token type. */ -public abstract class OAuth2ImplicitGrant implements OAuth2Grant { - - // Constants - - /** - * REQUIRED - * The "response_type" which MUST be "token". - */ - public final static String RESPONSE_TYPE = "token"; - - // Properties +abstract class OAuth2ImplicitGrant( /** * REQUIRED * The client identifier as described in https://tools.ietf.org/html/rfc6749#section-2.2. */ - public String clientId; + var clientId: String = "", /** * OPTIONAL * As described in https://tools.ietf.org/html/rfc6749#section-3.1.2. */ - public String redirectUri; + var redirectUri: String? = null, /** * OPTIONAL * The scope of the access request as described in https://tools.ietf.org/html/rfc6749#section-3.3. */ - public String scope; + var scope: String? = null, /** * RECOMMENDED @@ -45,5 +33,18 @@ public abstract class OAuth2ImplicitGrant The access token type. - */ -public abstract class OAuth2RefreshAccessTokenGrant implements OAuth2Grant { - - // Constants - - /** - * REQUIRED - * The OAuth2 "grant_type". - */ - public static final String GRANT_TYPE = "refresh_token"; - - // Properties - - /** - * REQUIRED - * The "refresh_token" issued to the client. - */ - public String refreshToken; - - /** - * OPTIONAL - * The "scope" of the access request as described by here (https://tools.ietf.org/html/rfc6749#section-3.3). - */ - public String scope; - -} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrant.kt b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrant.kt new file mode 100644 index 0000000..c76e81d --- /dev/null +++ b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrant.kt @@ -0,0 +1,27 @@ +package de.rheinfabrik.heimdall2.grants + +abstract class OAuth2RefreshAccessTokenGrant( + /** + * REQUIRED + * The "refresh_token" issued to the client. + */ + var refreshToken: String? = null, + + /** + * OPTIONAL + * The "scope" of the access request as described by here (https://tools.ietf.org/html/rfc6749#section-3.3). + */ + var scope: String? = null +) : + OAuth2Grant { + // Constants + + /** + * REQUIRED + * The OAuth2 "grant_type". + */ + companion object { + @JvmStatic + val GRANT_TYPE = "refresh_token" + } +} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrant.java b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrant.java deleted file mode 100644 index bd293d1..0000000 --- a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrant.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.rheinfabrik.heimdall2.grants; - -import de.rheinfabrik.heimdall2.OAuth2AccessToken; - -/** - * Class representing the Resource Owner Password Credentials Grant as described in https://tools.ietf.org/html/rfc6749#section-4.3. - * - * @param The access token type. - */ -public abstract class OAuth2ResourceOwnerPasswordCredentialsGrant implements OAuth2Grant { - - // Constants - - /** - * REQUIRED - * The OAuth2 "grant_type". - */ - public static final String GRANT_TYPE = "password"; - - // Properties - - /** - * REQUIRED - * The resource owner "username". - */ - public String username; - - /** - * REQUIRED - * The resource owner "password". - */ - public String password; - - /** - * OPTIONAL - * The "scope" of the access request as described by here (https://tools.ietf.org/html/rfc6749#section-3.3). - */ - public String scope; - -} diff --git a/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrant.kt b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrant.kt new file mode 100644 index 0000000..8b46863 --- /dev/null +++ b/library/src/main/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrant.kt @@ -0,0 +1,27 @@ +package de.rheinfabrik.heimdall2.grants + +abstract class OAuth2ResourceOwnerPasswordCredentialsGrant( + /** + * REQUIRED + * The resource owner "username". + */ + var username: String? = null, + + /** + * REQUIRED + * The resource owner "password". + */ + var password: String? = null, + + /** + * OPTIONAL + * The "scope" of the access request as described by here (https://tools.ietf.org/html/rfc6749#section-3.3). + */ + var scope: String? = null +) : OAuth2Grant { + + companion object { + @JvmStatic + val GRANT_TYPE = "password" + } +} diff --git a/library/src/test/groovy/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerSpecs.groovy b/library/src/test/groovy/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerSpecs.groovy deleted file mode 100644 index 069ffd7..0000000 --- a/library/src/test/groovy/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerSpecs.groovy +++ /dev/null @@ -1,227 +0,0 @@ -package de.rheinfabrik.heimdall2 - -import de.rheinfabrik.heimdall2.grants.OAuth2Grant -import de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant -import io.reactivex.Single -import spock.lang.Specification -import spock.lang.Title - -@Title("Tests for the constructor of the OAuth2AccessTokenManager class") -class OAuth2AccessTokenManagerConstructorSpecs extends Specification { - - // Scenarios - - @SuppressWarnings(["GroovyResultOfObjectAllocationIgnored", "GroovyAssignabilityCheck"]) - def "it should throw an exception if the storage argument is null"() { - - given: "A null storage" - OAuth2AccessTokenStorage storage = null - - when: "I initialize an OAuth2AccessTokenManager with a that storage" - new OAuth2AccessTokenManager(storage) - - then: "An IllegalArgumentException is thrown" - thrown(IllegalArgumentException) - } -} - -@Title("Tests for the grantNewAccessToken() function of the OAuth2AccessTokenManager class") -class OAuth2AccessTokenManagerGrantNewAccessTokenSpecs extends Specification { - - // Scenarios - - def "it should throw an IllegalArgumentException when the grant parameter is null"() { - - given: "A null grant" - OAuth2Grant grant = null - - and: "An OAuth2AccessTokenManager" - OAuth2AccessTokenManager tokenManager = new OAuth2AccessTokenManager(Mock(OAuth2AccessTokenStorage)) - - when: "I ask for a new access token" - tokenManager.grantNewAccessToken(grant) - - then: "An IllegalArgumentException is thrown" - thrown(IllegalArgumentException) - } - - def "it should generate and set the correct expiration date"() { - - given: "An OAuth2AccessToken" - OAuth2AccessToken accessToken = new OAuth2AccessToken(expirationDate: null) - accessToken.expiresIn = 3 - - and: "A grant emitting that token" - OAuth2Grant grant = Mock(OAuth2Grant) - grant.grantNewAccessToken() >> Single.just(accessToken) - - and: "An OAuth2AccessTokenManager" - OAuth2AccessTokenManager tokenManager = new OAuth2AccessTokenManager(Mock(OAuth2AccessTokenStorage)) - - and: "A calendar instance" - Calendar calendar = Calendar.getInstance() - - when: "I ask for a new access token" - OAuth2AccessToken newToken = tokenManager.grantNewAccessToken(grant, calendar).blockingGet() - - then: "The access token should have the correct expiration date" - newToken.expirationDate.timeInMillis == calendar.getTimeInMillis() + 3000 - } - - def "it should NOT generate and set the correct expiration date if expiresIn is null"() { - - given: "An OAuth2AccessToken" - OAuth2AccessToken accessToken = new OAuth2AccessToken(expirationDate: null) - accessToken.expiresIn = null - - and: "A grant emitting that token" - OAuth2Grant grant = Mock(OAuth2Grant) - grant.grantNewAccessToken() >> Single.just(accessToken) - - and: "An OAuth2AccessTokenManager" - OAuth2AccessTokenManager tokenManager = new OAuth2AccessTokenManager(Mock(OAuth2AccessTokenStorage)) - - when: "I ask for a new access token" - OAuth2AccessToken newToken = tokenManager.grantNewAccessToken(grant).blockingGet() - - then: "The access token should have the NO expiration date" - newToken.expirationDate == null - } - - def "it should store the access token"() { - - given: "An OAuth2AccessToken" - OAuth2AccessToken accessToken = new OAuth2AccessToken() - - and: "A grant emitting that token" - OAuth2Grant grant = Mock(OAuth2Grant) - grant.grantNewAccessToken() >> Single.just(accessToken) - - and: "A mock storage" - OAuth2AccessTokenStorage storage = Mock(OAuth2AccessTokenStorage) - - and: "An OAuth2AccessTokenManager" - OAuth2AccessTokenManager tokenManager = new OAuth2AccessTokenManager(storage) - - when: "I ask for a new access token" - tokenManager.grantNewAccessToken(grant).blockingGet() - - then: "The storage is asked to save the token" - 1 * storage.storeAccessToken(accessToken) - } -} - -@Title("Tests for the getStorage() function of the OAuth2AccessTokenManager class") -class OAuth2AccessTokenManagerGetStorageSpecs extends Specification { - - // Scenarios - - def "it should return the correct storage"() { - - given: "A mock storage" - OAuth2AccessTokenStorage storage = Mock(OAuth2AccessTokenStorage) - - and: "An OAuth2AccessTokenManager" - OAuth2AccessTokenManager tokenManager = new OAuth2AccessTokenManager(storage) - - when: "I ask for the storage" - OAuth2AccessTokenStorage receivedStorage = tokenManager.getStorage() - - then: "I have the storage I once passed via the constructor" - receivedStorage == storage - } -} - -@Title("Tests for the getValidAccessToken() function of the OAuth2AccessTokenManager class") -class OAuth2AccessTokenManagerGetValidAccessTokenSpecs extends Specification { - - // Scenarios - - def "it should throw an IllegalArgumentException when the refreshAccessTokenGrant parameter is null"() { - - given: "A null grant" - OAuth2RefreshAccessTokenGrant grant = null - - and: "An OAuth2AccessTokenManager" - OAuth2AccessTokenManager tokenManager = new OAuth2AccessTokenManager(Mock(OAuth2AccessTokenStorage)) - - when: "I ask for a valid access token" - tokenManager.getValidAccessToken(grant) - - then: "An IllegalArgumentException is thrown" - thrown(IllegalArgumentException) - } - - def "it should emit the non-expired stored access token"() { - - given: "A non-expired OAuth2AccessToken" - OAuth2AccessToken accessToken = Mock(OAuth2AccessToken) - accessToken.isExpired() >> false - - and: "A mock storage emitting that token" - OAuth2AccessTokenStorage storage = Mock(OAuth2AccessTokenStorage) - storage.getStoredAccessToken() >> Single.just(accessToken) - - and: "A mock grant" - OAuth2RefreshAccessTokenGrant grant = Mock(OAuth2RefreshAccessTokenGrant) - grant.grantNewAccessToken() >> Single.just(accessToken) - - and: "An OAuth2AccessTokenManager with that storage" - OAuth2AccessTokenManager tokenManager = new OAuth2AccessTokenManager(storage) - - when: "I ask for a valid access token" - OAuth2AccessToken validToken = tokenManager.getValidAccessToken(grant).blockingGet() - - then: "The I receive the non-expired token" - validToken == accessToken - } - - @SuppressWarnings("GroovyAssignabilityCheck") - def "it should ask to refresh the token if the token is expired"() { - - given: "An expired OAuth2AccessToken" - OAuth2AccessToken accessToken = Mock(OAuth2AccessToken) - accessToken.isExpired() >> true - - and: "A mock storage emitting that token" - OAuth2AccessTokenStorage storage = Mock(OAuth2AccessTokenStorage) - storage.getStoredAccessToken() >> Single.just(accessToken) - - and: "An OAuth2AccessTokenManager with that storage" - OAuth2AccessTokenManager tokenManager = new OAuth2AccessTokenManager(storage) - - and: "A mock grant" - OAuth2RefreshAccessTokenGrant grant = Mock(OAuth2RefreshAccessTokenGrant) - - when: "I ask for a valid access token" - tokenManager.getValidAccessToken(grant).subscribe() - - then: "The refresh grant is asked for a new token" - 1 * grant.grantNewAccessToken() >> Single.just(accessToken) - } - - def "it should set the refresh token to the grant if the token is expired"() { - - given: "An expired OAuth2AccessToken" - OAuth2AccessToken accessToken = Mock(OAuth2AccessToken) - accessToken.refreshToken = "rt" - accessToken.isExpired() >> true - - and: "A mock storage emitting that token" - OAuth2AccessTokenStorage storage = Mock(OAuth2AccessTokenStorage) - storage.getStoredAccessToken() >> Single.just(accessToken) - - and: "An OAuth2AccessTokenManager with that storage" - OAuth2AccessTokenManager tokenManager = new OAuth2AccessTokenManager(storage) - - and: "A mock grant" - OAuth2RefreshAccessTokenGrant grant = Mock(OAuth2RefreshAccessTokenGrant) - grant.grantNewAccessToken() >> Single.just(accessToken) - - when: "I ask for a valid access token" - tokenManager.getValidAccessToken(grant).subscribe() - - then: "The refresh grant is asked for a new token" - grant.refreshToken == accessToken.refreshToken - } -} diff --git a/library/src/test/groovy/de/rheinfabrik/heimdall2/OAuth2AccessTokenSpecs.groovy b/library/src/test/groovy/de/rheinfabrik/heimdall2/OAuth2AccessTokenSpecs.groovy deleted file mode 100644 index 2120717..0000000 --- a/library/src/test/groovy/de/rheinfabrik/heimdall2/OAuth2AccessTokenSpecs.groovy +++ /dev/null @@ -1,107 +0,0 @@ -package de.rheinfabrik.heimdall2 - -import com.google.gson.Gson -import spock.lang.Specification -import spock.lang.Title - -@Title("Specs for serialization in the OAuth2AccessToken class.") -class OAuth2AccessTokenSerializationSpecs extends Specification { - - // Setup - - def setup() { - - // Set default locale and time zone - Locale.setDefault(Locale.GERMANY) - TimeZone.setDefault(TimeZone.getTimeZone("CEST")) - } - - // Scenarios - - def "It should create the correct JSON for a given OAuth2AccessToken"() { - - given: "An OAuth2AccessToken" - OAuth2AccessToken accessToken = new OAuth2AccessToken(refreshToken: "rt", expiresIn: 3600, accessToken: "at", tokenType: "bearer") - accessToken.expirationDate = Calendar.getInstance() - accessToken.expirationDate.setTimeInMillis(0) - - when: "I serialize it with Gson" - String json = new Gson().toJson(accessToken) - - then: "The JSON should be as expected" - json == "{\"token_type\":\"bearer\",\"access_token\":\"at\",\"refresh_token\":\"rt\",\"expires_in\":3600,\"heimdall_expiration_date\":{\"year\":1970,\"month\":0,\"dayOfMonth\":1,\"hourOfDay\":0,\"minute\":0,\"second\":0}}" - } - - def "It should create the correct OAuth2AccessToken for a given JSON"() { - - given: "Some JSON representing an OAuth2AccessToken" - String json = "{\"access_token\":\"at\",\"heimdall_expiration_date\":{\"year\":1970,\"month\":0,\"dayOfMonth\":1,\"hourOfDay\":0,\"minute\":0,\"second\":0},\"expires_in\":3600,\"refresh_token\":\"rt\",\"token_type\":\"bearer\"}" - - when: "I deserialize it with Gson" - OAuth2AccessToken accessToken = new Gson().fromJson(json, OAuth2AccessToken.class) - - then: "The OAuth2AccessToken should be as expected" - accessToken.refreshToken == "rt" - accessToken.expiresIn == 3600 - accessToken.accessToken == "at" - accessToken.tokenType == "bearer" - - Calendar calendar = Calendar.getInstance() - calendar.setTimeInMillis(0) - accessToken.expirationDate.timeInMillis == calendar.timeInMillis - } -} - -@Title("Specs for the isExpired() function in the OAuth2AccessToken class.") -class OAuth2AccessTokenIsExpiredSpecs extends Specification { - - // Scenarios - - @SuppressWarnings("GroovyPointlessBoolean") - def "It should return false if the expirationDate is null"() { - - given: "An OAuth2AccessToken with null as expirationDate" - OAuth2AccessToken accessToken = new OAuth2AccessToken(expirationDate: null) - - when: "I check if the access token is expired" - boolean isExpired = accessToken.isExpired() - - then: "It should be true" - isExpired == false - } - - @SuppressWarnings("GroovyPointlessBoolean") - def "It should return false if the expirationDate is in the future"() { - - given: "A date which is in the future" - Calendar future = Calendar.getInstance() - future.add(Calendar.YEAR, 1) - - and: "An OAuth2AccessToken with that future as expirationDate" - OAuth2AccessToken accessToken = new OAuth2AccessToken(expirationDate: future) - - when: "I check if the access token is expired" - boolean isExpired = accessToken.isExpired() - - then: "It should be false" - isExpired == false - } - - @SuppressWarnings("GroovyPointlessBoolean") - def "It should return true if the expirationDate is in the past"() { - - given: "A date which is in the past" - Calendar past = Calendar.getInstance() - past.add(Calendar.YEAR, -1) - - and: "An OAuth2AccessToken with that past as expirationDate" - OAuth2AccessToken accessToken = new OAuth2AccessToken(expirationDate: past) - - when: "I check if the access token is expired" - boolean isExpired = accessToken.isExpired() - - then: "It should be true" - isExpired == true - } - -} diff --git a/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrantSpecs.groovy b/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrantSpecs.groovy deleted file mode 100644 index f2effea..0000000 --- a/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrantSpecs.groovy +++ /dev/null @@ -1,22 +0,0 @@ -package de.rheinfabrik.heimdall2.grants - -import spock.lang.Specification -import spock.lang.Title - -@Title("Specs for the OAuth2AuthorizationCodeGrant") -class OAuth2AuthorizationCodeGrantSpecs extends Specification { - - // Scenarios - - def "It should have the correct response type as described in https://tools.ietf.org/html/rfc6749#section-4.1"() { - - expect: "the response type to be code" - OAuth2AuthorizationCodeGrant.RESPONSE_TYPE == "code" - } - - def "It should have the correct grant type as described in https://tools.ietf.org/html/rfc6749#section-4.1"() { - - expect: "the response type to be authorization_code" - OAuth2AuthorizationCodeGrant.GRANT_TYPE == "authorization_code" - } -} diff --git a/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrantSpecs.groovy b/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrantSpecs.groovy deleted file mode 100644 index 7cf782d..0000000 --- a/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrantSpecs.groovy +++ /dev/null @@ -1,17 +0,0 @@ -package de.rheinfabrik.heimdall2.grants - -import spock.lang.Specification -import spock.lang.Title - -@Title("Specs for the OAuth2ClientCredentialsGrant") -class OAuth2ClientCredentialsGrantSpecs extends Specification { - - // Scenarios - - def "It should have the correct grant type as described in https://tools.ietf.org/html/rfc6749#section-4.4"() { - - expect: "the grant type to be client_credentials" - OAuth2ClientCredentialsGrant.GRANT_TYPE == "client_credentials" - } - -} diff --git a/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrantSpecs.groovy b/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrantSpecs.groovy deleted file mode 100644 index bdea92e..0000000 --- a/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrantSpecs.groovy +++ /dev/null @@ -1,17 +0,0 @@ -package de.rheinfabrik.heimdall2.grants - -import spock.lang.Specification -import spock.lang.Title - -@Title("Specs for the OAuth2ImplicitGrant") -class OAuth2ImplicitGrantSpecs extends Specification { - - // Scenarios - - def "It should have the correct response type as described in https://tools.ietf.org/html/rfc6749#section-4.2"() { - - expect: "the response type to be token" - OAuth2ImplicitGrant.RESPONSE_TYPE == "token" - } - -} diff --git a/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrantSpecs.groovy b/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrantSpecs.groovy deleted file mode 100644 index 40ccad9..0000000 --- a/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrantSpecs.groovy +++ /dev/null @@ -1,17 +0,0 @@ -package de.rheinfabrik.heimdall2.grants - -import spock.lang.Specification -import spock.lang.Title - -@Title("Specs for the OAuth2RefreshAccessTokenGrant") -class OAuth2RefreshAccessTokenGrantSpecs extends Specification { - - // Scenarios - - def "It should have the correct grant type as described in https://tools.ietf.org/html/rfc6749#section-6"() { - - expect: "the grant type to be refresh_token" - OAuth2RefreshAccessTokenGrant.GRANT_TYPE == "refresh_token" - } - -} diff --git a/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrantSpecs.groovy b/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrantSpecs.groovy deleted file mode 100644 index 426c7bd..0000000 --- a/library/src/test/groovy/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrantSpecs.groovy +++ /dev/null @@ -1,17 +0,0 @@ -package de.rheinfabrik.heimdall2.grants - -import spock.lang.Specification -import spock.lang.Title - -@Title("Specs for the OAuth2ResourceOwnerPasswordCredentialsGrant") -class OAuth2ResourceOwnerPasswordCredentialsGrantSpecs extends Specification { - - // Scenarios - - def "It should have the correct grant type as described in https://tools.ietf.org/html/rfc6749#section-4.3"() { - - expect: "the grant type to be password" - OAuth2ResourceOwnerPasswordCredentialsGrant.GRANT_TYPE == "password" - } - -} diff --git a/library/src/test/java/de/rheinfabrik/heimdall2/EmptyTestClass.java b/library/src/test/java/de/rheinfabrik/heimdall2/EmptyTestClass.java deleted file mode 100644 index eb4ca9c..0000000 --- a/library/src/test/java/de/rheinfabrik/heimdall2/EmptyTestClass.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.rheinfabrik.heimdall2; - -public class EmptyTestClass { - // This is an empty class. It exists only because the gradle compileTestGroovy - // build step for some reason does not properly compile the Groovy tests if there is - // not at least one class in the Java test directory -} diff --git a/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenIsExpiredTest.kt b/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenIsExpiredTest.kt new file mode 100644 index 0000000..f126e8e --- /dev/null +++ b/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenIsExpiredTest.kt @@ -0,0 +1,53 @@ +package de.rheinfabrik.heimdall2 + +import org.junit.Assert.assertEquals +import org.junit.Test +import java.util.* + +class OAuth2AccessTokenIsExpiredTest { + + @Test + fun `when the expirationDate is null, isExpired method should return false`() { + // given an accessToken + val accessToken = OAuth2AccessToken(expirationDate = null) + + // when the isExpired method is called + val value = accessToken.isExpired() + + // then false is returned + assertEquals(value, false) + + } + + @Test + fun `when the expirationDate is in the past, isExpired method should return false`() { + // given a date in the past + val pastCalendar = Calendar.getInstance() + pastCalendar.add(Calendar.YEAR, 1) + + // and a token with a past date + val accessToken = OAuth2AccessToken(expirationDate = pastCalendar) + + // when the isExpired method is called + val value = accessToken.isExpired() + + // then true is returned + assertEquals(value, false) + } + + @Test + fun `when the expirationDate is in the future, isExpired method should return false`() { + // given a date in the past + val futureDate = Calendar.getInstance() + futureDate.add(Calendar.YEAR, 1) + + // and a token with a past date + val accessToken = OAuth2AccessToken(expirationDate = futureDate) + + // when the isExpired method is called + val value = accessToken.isExpired() + + // then true is returned + assertEquals(value, false) + } +} diff --git a/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGetValidAccessTokenTest.kt b/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGetValidAccessTokenTest.kt new file mode 100644 index 0000000..693503e --- /dev/null +++ b/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGetValidAccessTokenTest.kt @@ -0,0 +1,96 @@ +package de.rheinfabrik.heimdall2 + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever +import de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant +import io.reactivex.Single +import org.junit.Test +import java.util.* + +class OAuth2AccessTokenManagerGetValidAccessTokenTest { + + @Test + fun `when subscribed to getValidAccessToken(), the non-expired token should be emitted`() { + + // given a non expired token + val accessToken = mock().apply { + whenever(isExpired()).thenReturn(false) + } + + // and a token manager with a valid storage and token + val storage = mock().apply { + whenever(getStoredAccessToken()).thenReturn(Single.just(accessToken)) + } + val grant = mock().apply { + whenever(grantNewAccessToken()).thenReturn(Single.just(accessToken)) + } + val tokenManager = OAuth2AccessTokenManager( + mStorage = storage + ) + + // when a valid access token is requested + val validTokenTest = tokenManager.getValidAccessToken(grant).test() + + // then the non expired token gets received + validTokenTest.assertValue(accessToken) + } + + @Test + fun `when the token expires, the refresh grant should be called to refresh it`(){ + // given an expired accesstoken + val accessToken = mock().apply { + whenever(isExpired()).thenReturn(true) + } + + // and a token manager + val tokenManager = OAuth2AccessTokenManager( + mStorage = mock().apply { + whenever(getStoredAccessToken()).thenReturn(Single.just(accessToken)) + } + ) + + // and a OAuth2RefreshAccessTokenGrant + val grant = mock() + + // when a valid token is needed + tokenManager.getValidAccessToken(grant).test() + + // then a refresh grant asks for a new token + verify(grant).grantNewAccessToken() + + } + + @Test + fun `when the token expires, the grant should be updated with a new token`() { + // given an expired OAuthAccessToken + val pastDate = Calendar.getInstance() + pastDate.add(Calendar.YEAR, -1) + + val accessToken = OAuth2AccessToken( + refreshToken = "rt", + expirationDate = pastDate + ) + + // and a token manager + val tokenManager = OAuth2AccessTokenManager( + mStorage = mock().apply { + whenever(getStoredAccessToken()).thenReturn(Single.just(accessToken)) + } + ) + + // and a grant + val grant = mock().apply { + whenever(grantNewAccessToken()).thenReturn(Single.just(accessToken)) + } + + // when a valid access token is needed + val grantToken = tokenManager.getValidAccessToken(grant).test() + + // then the grants new refreshToken gets updated + grantToken.assertValue{ + it.refreshToken == accessToken.refreshToken + } + + } +} diff --git a/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGrantNewAccessTokenTest.kt b/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGrantNewAccessTokenTest.kt new file mode 100644 index 0000000..431cbff --- /dev/null +++ b/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerGrantNewAccessTokenTest.kt @@ -0,0 +1,98 @@ +package de.rheinfabrik.heimdall2 + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever +import de.rheinfabrik.heimdall2.grants.OAuth2Grant +import io.reactivex.Single +import org.junit.Test +import java.util.* + +class OAuth2AccessTokenManagerGrantNewAccessTokenTest { + + @Test + fun `when a new access token has an expiration date, the token manager should generate and set the correct expiration date`() { + + // given an OAuth2AccessToken + val accessToken = OAuth2AccessToken( + expirationDate = null + ) + accessToken.expiresIn = 3 + + // and a grant that emits that token + val grant = mock().apply { + whenever(grantNewAccessToken()).thenReturn(Single.just(accessToken)) + } + + // and a tokenManager + val tokenManager = OAuth2AccessTokenManager( + mStorage = mock() + ) + + // when a new access token is needed + val calendar = Calendar.getInstance() + val newToken = tokenManager.grantNewAccessToken( + grant = grant, + calendar = calendar + ).test() + + // then the access token should have the correct expiration date + newToken.assertValue { + it.expirationDate?.timeInMillis == calendar.timeInMillis + 3000 + } + } + + @Test + fun `when a new access token doesn't have an expiration date, the token manager should not generate and a new expiration date`() { + + // given an OAuth2AccessToken + val accessToken = OAuth2AccessToken( + expirationDate = null + ) + accessToken.expiresIn = null + + // and a grant that emits that token + val grant = mock().apply { + whenever(grantNewAccessToken()).thenReturn(Single.just(accessToken)) + } + + // and a tokenManager + val tokenManager = OAuth2AccessTokenManager( + mStorage = mock() + ) + + // when a new access token is needed + val calendar = Calendar.getInstance() + val newToken = tokenManager.grantNewAccessToken( + grant = grant, + calendar = calendar + ).test() + + // then the access token should have the correct expiration date + newToken.assertValue { + it.expirationDate == null + } + } + + @Test + fun `when a new access token is needed, the storage should be called to store the token`() { + // given a grant that emits a token + val accessToken = OAuth2AccessToken() + val grant = mock().apply { + whenever(grantNewAccessToken()).thenReturn(Single.just(accessToken)) + } + + // and a token manager + val storage = mock() + val tokenManager = OAuth2AccessTokenManager( + mStorage = storage + ) + + // when a new access token is needed + tokenManager.grantNewAccessToken(grant).test() + + // then the storage is called + verify(storage).storeAccessToken(accessToken) + } + +} diff --git a/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerTest.kt b/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerTest.kt new file mode 100644 index 0000000..f0de107 --- /dev/null +++ b/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenManagerTest.kt @@ -0,0 +1,32 @@ +package de.rheinfabrik.heimdall2 + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import de.rheinfabrik.heimdall2.grants.OAuth2RefreshAccessTokenGrant +import io.reactivex.Single +import org.junit.Test + + +class OAuth2AccessTokenManagerTest { + + @Test + fun `when the access token is accessed, the token manager should emit a non expired token`(){ + // given a stored access token + val validToken = mock() + val accessTokenStorage = mock() + whenever(accessTokenStorage.getStoredAccessToken()).thenReturn(Single.just(validToken)) + + // and a refresh access token grant + val refreshAccessTokenGrant = mock() + whenever(refreshAccessTokenGrant.grantNewAccessToken()).thenReturn(Single.just(validToken)) + + // and a token manager + val tokenManager = OAuth2AccessTokenManager(accessTokenStorage) + + // when the token is accessed + val getValidAccessTokenTest = tokenManager.getValidAccessToken(refreshAccessTokenGrant).test() + + // then the token is received + getValidAccessTokenTest.assertValue(validToken) + } +} \ No newline at end of file diff --git a/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenSerializationTest.kt b/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenSerializationTest.kt new file mode 100644 index 0000000..07281b3 --- /dev/null +++ b/library/src/test/java/de/rheinfabrik/heimdall2/OAuth2AccessTokenSerializationTest.kt @@ -0,0 +1,64 @@ +package de.rheinfabrik.heimdall2 + +import com.google.gson.Gson +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import java.util.* + + +class OAuth2AccessTokenSerializationTest { + + @Before + fun setup() { + // Set default locale and time zone + Locale.setDefault(Locale.GERMANY) + TimeZone.setDefault(TimeZone.getTimeZone("CEST")) + } + + @Test + fun `when an OAuth2AccessToken is transformed to a JSON string, it should be created`() { + + // given an OAuth2AccessToken + val accessToken = OAuth2AccessToken( + refreshToken = "rt", + expiresIn = 3600, + accessToken = "at", + tokenType = "bearer" + ) + val expirationDate = Calendar.getInstance() + expirationDate.timeInMillis = 0 + accessToken.expirationDate = expirationDate + + // when it gets serialized with Gson + val json = Gson().toJson(accessToken) + + // then the json should be written correctly + assertEquals( + "{\"token_type\":\"bearer\",\"access_token\":\"at\",\"refresh_token\":\"rt\",\"expires_in\":3600,\"heimdall_expiration_date\":{\"year\":1970,\"month\":0,\"dayOfMonth\":1,\"hourOfDay\":0,\"minute\":0,\"second\":0}}", + json + ) + } + + @Test + fun `when a token is created from a JSON string, it should be created`() { + + // given a json string representing a OAuth2AccessToken + val jsonString = + "{\"access_token\":\"at\",\"heimdall_expiration_date\":{\"year\":1970,\"month\":0,\"dayOfMonth\":1,\"hourOfDay\":0,\"minute\":0,\"second\":0},\"expires_in\":3600,\"refresh_token\":\"rt\",\"token_type\":\"bearer\"}" + + // and an expiration date + val calendar = Calendar.getInstance() + calendar.timeInMillis = 0 + + // when it gets deserialized with Gson + val accessToken = Gson().fromJson(jsonString, OAuth2AccessToken::class.java) + + // then the token should be the same + assertEquals("rt", accessToken.refreshToken) + assertEquals(3600, accessToken.expiresIn) + assertEquals("at", accessToken.accessToken) + assertEquals("bearer", accessToken.tokenType) + assertEquals(calendar.timeInMillis, accessToken.expirationDate?.timeInMillis) + } +} \ No newline at end of file diff --git a/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrantTest.kt b/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrantTest.kt new file mode 100644 index 0000000..ee2a59d --- /dev/null +++ b/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2AuthorizationCodeGrantTest.kt @@ -0,0 +1,21 @@ +package de.rheinfabrik.heimdall2.grants + +import org.junit.Assert.assertEquals +import org.junit.Test + +class OAuth2AuthorizationCodeGrantTest { + + // Specifications for https://tools.ietf.org/html/rfc6749#section-4.1 + + @Test + fun `when an authorization code grant is created, the grant type is code`() { + // the grant type is the correct + assertEquals(OAuth2AuthorizationCodeGrant.GRANT_TYPE, "authorization_code") + } + + @Test + fun `when an authorization code grant is created, the response type is code`() { + // the grant type is the correct + assertEquals(OAuth2AuthorizationCodeGrant.RESPONSE_TYPE, "code") + } +} diff --git a/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrantTest.kt b/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrantTest.kt new file mode 100644 index 0000000..55c2398 --- /dev/null +++ b/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ClientCredentialsGrantTest.kt @@ -0,0 +1,18 @@ +package de.rheinfabrik.heimdall2.grants + +import org.junit.Assert.assertEquals +import org.junit.Test + +class OAuth2ClientCredentialsGrantTest { + + // Specification for https://tools.ietf.org/html/rfc6749#section-4.4 + + @Test + fun `when a client credentials grant is created, the grant type is code`() { + // when an client credentials grant is created + val grant = OAuth2ClientCredentialsGrant + + // then the grant type is the correct + assertEquals(grant.GRANT_TYPE, "client_credentials") + } +} diff --git a/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrantTest.kt b/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrantTest.kt new file mode 100644 index 0000000..4416812 --- /dev/null +++ b/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ImplicitGrantTest.kt @@ -0,0 +1,18 @@ +package de.rheinfabrik.heimdall2.grants + +import org.junit.Assert.assertEquals +import org.junit.Test + +class OAuth2ImplicitGrantTest { + + // Specifications for https://tools.ietf.org/html/rfc6749#section-4.2 + + @Test + fun `when an implicit grant is created, the response type is token`() { + // when an authorization code grant is created + val grant = OAuth2ImplicitGrant + + // then the grant type is the correct + assertEquals(grant.RESPONSE_TYPE, "token") + } +} diff --git a/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrantTest.kt b/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrantTest.kt new file mode 100644 index 0000000..ef9904e --- /dev/null +++ b/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2RefreshAccessTokenGrantTest.kt @@ -0,0 +1,15 @@ +package de.rheinfabrik.heimdall2.grants + +import org.junit.Assert.assertEquals +import org.junit.Test + +class OAuth2RefreshAccessTokenGrantTest { + + // Specifications for https://tools.ietf.org/html/rfc6749#section-6 + + @Test + fun `when an refresh access token grant is created, the grant type is refresh_token`() { + // the grant type is the correct + assertEquals(OAuth2RefreshAccessTokenGrant.GRANT_TYPE, "refresh_token") + } +} diff --git a/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrantTest.kt b/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrantTest.kt new file mode 100644 index 0000000..4cc9027 --- /dev/null +++ b/library/src/test/java/de/rheinfabrik/heimdall2/grants/OAuth2ResourceOwnerPasswordCredentialsGrantTest.kt @@ -0,0 +1,15 @@ +package de.rheinfabrik.heimdall2.grants + +import org.junit.Assert.assertEquals +import org.junit.Test + +class OAuth2ResourceOwnerPasswordCredentialsGrantTest { + + // Specifications for https://tools.ietf.org/html/rfc6749#section-4.3 + + @Test + fun `when a access token grant is created, the grant type is refresh_token`() { + // the grant type is the correct + assertEquals(OAuth2ResourceOwnerPasswordCredentialsGrant.GRANT_TYPE, "password") + } +} diff --git a/library/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/library/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000..ca6ee9c --- /dev/null +++ b/library/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/LoginActivity.java b/sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/LoginActivity.java index 4f613eb..54105fb 100644 --- a/sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/LoginActivity.java +++ b/sample/src/main/java/de/rheinfabrik/heimdalldroid/actvities/LoginActivity.java @@ -18,6 +18,7 @@ import de.rheinfabrik.heimdalldroid.network.oauth2.TraktTvAuthorizationCodeGrant; import de.rheinfabrik.heimdalldroid.network.oauth2.TraktTvOauth2AccessTokenManager; import de.rheinfabrik.heimdalldroid.utils.AlertDialogFactory; +import java.util.Calendar; /** * Activity used to let the user login with his GitHub credentials. @@ -73,10 +74,10 @@ public void onPageStarted(WebView view, String urlString, Bitmap favicon) { try { URL url = new URL(urlString); - grant.onUrlLoadedCommand.onNext(url); + grant.getOnUrlLoadedCommand().onNext(url); // Hide redirect page from user - if (urlString.startsWith(grant.redirectUri)) { + if (urlString.startsWith(grant.getRedirectUri())) { mWebView.setVisibility(View.GONE); } } catch (MalformedURLException ignored) { @@ -87,7 +88,7 @@ public void onPageStarted(WebView view, String urlString, Bitmap favicon) { }); // Start authorization and listen for success - tokenManager.grantNewAccessToken(grant) + tokenManager.grantNewAccessToken(grant, Calendar.getInstance()) .toObservable() .compose(bindToLifecycle()) .observeOn(AndroidSchedulers.mainThread()) diff --git a/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvAuthorizationCodeGrant.java b/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvAuthorizationCodeGrant.java index f9e02de..bf34834 100644 --- a/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvAuthorizationCodeGrant.java +++ b/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvAuthorizationCodeGrant.java @@ -2,18 +2,18 @@ import android.net.Uri; +import de.rheinfabrik.heimdall2.grants.OAuth2AuthorizationCodeGrant; import io.reactivex.Observable; import java.net.URL; import de.rheinfabrik.heimdall2.OAuth2AccessToken; -import de.rheinfabrik.heimdall2.grants.OAuth2AuthorizationCodeGrant; import de.rheinfabrik.heimdalldroid.network.TraktTvApiFactory; import de.rheinfabrik.heimdalldroid.network.models.AccessTokenRequestBody; /** * TraktTv authorization code grant as described in http://docs.trakt.apiary.io/#reference/authentication-oauth. */ -public class TraktTvAuthorizationCodeGrant extends OAuth2AuthorizationCodeGrant { +public class TraktTvAuthorizationCodeGrant extends OAuth2AuthorizationCodeGrant { // Properties @@ -27,9 +27,9 @@ public URL buildAuthorizationUrl() { return new URL( Uri.parse("https://trakt.tv/oauth/authorize") .buildUpon() - .appendQueryParameter("client_id", clientId) - .appendQueryParameter("redirect_uri", redirectUri) - .appendQueryParameter("response_type", RESPONSE_TYPE) + .appendQueryParameter("client_id", getClientId()) + .appendQueryParameter("redirect_uri", getRedirectUri()) + .appendQueryParameter("response_type", OAuth2AuthorizationCodeGrant.getRESPONSE_TYPE()) .build() .toString() ); @@ -40,7 +40,9 @@ public URL buildAuthorizationUrl() { @Override public Observable exchangeTokenUsingCode(String code) { - AccessTokenRequestBody body = new AccessTokenRequestBody(code, clientId, redirectUri, clientSecret, GRANT_TYPE); + AccessTokenRequestBody body = new AccessTokenRequestBody( + code, getClientId(), getRedirectUri(), clientSecret, getGRANT_TYPE() + ); return TraktTvApiFactory.newApiService().grantNewAccessToken(body); } } diff --git a/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvOauth2AccessTokenManager.java b/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvOauth2AccessTokenManager.java index 9cf95a9..de8aed9 100644 --- a/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvOauth2AccessTokenManager.java +++ b/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvOauth2AccessTokenManager.java @@ -15,7 +15,7 @@ /** * Token manger used to handle all your access token needs with the TraktTv API (http://docs.trakt.apiary.io/#). */ -public final class TraktTvOauth2AccessTokenManager extends OAuth2AccessTokenManager { +public final class TraktTvOauth2AccessTokenManager extends OAuth2AccessTokenManager { // Factory methods @@ -36,7 +36,7 @@ public static TraktTvOauth2AccessTokenManager from(Context context) { // Constructor - public TraktTvOauth2AccessTokenManager(OAuth2AccessTokenStorage storage) { + public TraktTvOauth2AccessTokenManager(OAuth2AccessTokenStorage storage) { super(storage); } @@ -47,9 +47,9 @@ public TraktTvOauth2AccessTokenManager(OAuth2AccessTokenStorage getValidAccessToken() { grant.clientSecret = TraktTvAPIConfiguration.CLIENT_SECRET; grant.redirectUri = TraktTvAPIConfiguration.REDIRECT_URI; - return super.getValidAccessToken(grant).map(token -> token.tokenType + " " + token.accessToken); + return super.getValidAccessToken(grant).map(token -> token.getTokenType() + " " + token.getAccessToken()); } /** @@ -74,7 +74,7 @@ public Single logout() { .toObservable() .filter(token -> token != null) .concatMap(accessToken -> { - RevokeAccessTokenBody body = new RevokeAccessTokenBody(accessToken.accessToken); + RevokeAccessTokenBody body = new RevokeAccessTokenBody(accessToken.getAccessToken()); return TraktTvApiFactory.newApiService().revokeAccessToken(body); }) .doOnNext(x -> getStorage().removeAccessToken()).singleOrError(); diff --git a/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvRefreshAccessTokenGrant.java b/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvRefreshAccessTokenGrant.java index 7359c99..2fad1fd 100644 --- a/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvRefreshAccessTokenGrant.java +++ b/sample/src/main/java/de/rheinfabrik/heimdalldroid/network/oauth2/TraktTvRefreshAccessTokenGrant.java @@ -21,7 +21,7 @@ public class TraktTvRefreshAccessTokenGrant extends OAuth2RefreshAccessTokenGran @Override public Single grantNewAccessToken() { - RefreshTokenRequestBody body = new RefreshTokenRequestBody(refreshToken, clientId, clientSecret, redirectUri, GRANT_TYPE); + RefreshTokenRequestBody body = new RefreshTokenRequestBody(getRefreshToken(), clientId, clientSecret, redirectUri, getGRANT_TYPE()); return TraktTvApiFactory.newApiService().refreshAccessToken(body).singleOrError(); } }