Skip to content

Commit

Permalink
feat: configuration update with organization name (#217)
Browse files Browse the repository at this point in the history
- move organizationName property to the CrowdinConfig with backward compatibility;
- deprecate usage in AuthConfig;
- add new check in config builder for organization name when hash matches enterprise project;
- documentation update;
  • Loading branch information
MykhailoNester authored Sep 12, 2023
1 parent 7f498c5 commit 744634b
Show file tree
Hide file tree
Showing 19 changed files with 80 additions and 31 deletions.
4 changes: 3 additions & 1 deletion crowdin/src/main/java/com/crowdin/platform/Crowdin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,8 @@ object Crowdin {

internal fun getAuthConfig(): AuthConfig? = config.authConfig

internal fun getOrganizationName(): String? = config.organizationName

private fun initTranslationDataManager() {
if (FeatureFlags.isRealTimeUpdateEnabled) {
translationDataRepository = TranslationDataRepository(
Expand All @@ -571,7 +573,7 @@ object Crowdin {
private fun getCrowdinApi(): CrowdinApi =
CrowdinRetrofitService.getCrowdinApi(
dataManager!!,
config.authConfig?.organizationName
config.organizationName
)

fun getManifest(): ManifestData? {
Expand Down
24 changes: 24 additions & 0 deletions crowdin/src/main/java/com/crowdin/platform/CrowdinConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class CrowdinConfig private constructor() {
var sourceLanguage: String = ""
var authConfig: AuthConfig? = null
var isInitSyncEnabled: Boolean = true
var organizationName: String? = null

class Builder {

Expand All @@ -31,6 +32,7 @@ class CrowdinConfig private constructor() {
private var sourceLanguage: String = ""
private var authConfig: AuthConfig? = null
private var isInitSyncEnabled: Boolean = true
private var organizationName: String? = null

fun persist(isPersist: Boolean): Builder {
this.isPersist = isPersist
Expand Down Expand Up @@ -69,6 +71,10 @@ class CrowdinConfig private constructor() {

fun withAuthConfig(authConfig: AuthConfig): Builder {
this.authConfig = authConfig
// Required for backward compatibility
authConfig.organizationName?.let {
this.organizationName = it
}
return this
}

Expand All @@ -77,13 +83,27 @@ class CrowdinConfig private constructor() {
return this
}

fun withOrganizationName(organizationName: String): Builder {
this.organizationName = organizationName
return this
}

fun build(): CrowdinConfig {
val config = CrowdinConfig()
config.isPersist = isPersist
require(distributionHash.isNotEmpty()) { "Crowdin: `distributionHash` cannot be empty" }

config.distributionHash = distributionHash

if (distributionHash.startsWith(ORGANIZATION_PREFIX) && organizationName.isNullOrEmpty()) {
Log.w(
Crowdin.CROWDIN_TAG,
"Crowdin: the `organizationName` cannot be empty for Crowdin Enterprise. Add it to the `CrowdingConfig` " +
"using the `.withOrganizationName(...)` method"
)
}

config.organizationName = organizationName
config.networkType = networkType
config.isRealTimeUpdateEnabled = isRealTimeUpdateEnabled
config.isScreenshotEnabled = isScreenshotEnabled
Expand Down Expand Up @@ -116,5 +136,9 @@ class CrowdinConfig private constructor() {

return config
}

companion object {
private const val ORGANIZATION_PREFIX = "e-"
}
}
}
2 changes: 1 addition & 1 deletion crowdin/src/main/java/com/crowdin/platform/Session.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal interface Session {

fun getAccessToken(): String?

fun refreshToken(authConfig: AuthConfig?): Boolean
fun refreshToken(organizationName: String?, authConfig: AuthConfig?): Boolean

/**
* Executed on background thread.
Expand Down
5 changes: 2 additions & 3 deletions crowdin/src/main/java/com/crowdin/platform/SessionImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,17 @@ internal class SessionImpl(

override fun getAccessToken(): String? = dataManager.getAccessToken()

override fun refreshToken(authConfig: AuthConfig?): Boolean {
override fun refreshToken(organizationName: String?, authConfig: AuthConfig?): Boolean {
val refreshToken = dataManager.getRefreshToken()
refreshToken ?: return false

val clientId = authConfig?.clientId ?: ""
val clientSecret = authConfig?.clientSecret ?: ""
val domain = authConfig?.organizationName

var response: Response<AuthResponse>? = null
executeIO {
response = authApi.getToken(
RefreshToken("refresh_token", clientId, clientSecret, refreshToken), domain
RefreshToken("refresh_token", clientId, clientSecret, refreshToken), organizationName
).execute()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ internal class AuthActivity : AppCompatActivity() {
@SuppressLint("SetJavaScriptEnabled")
private fun requestAuthorization() {
val authConfig = Crowdin.getAuthConfig()
domain = Crowdin.getOrganizationName()
clientId = authConfig?.clientId ?: ""
clientSecret = authConfig?.clientSecret ?: ""
domain = authConfig?.organizationName

val builder = Uri.Builder()
.scheme("https")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
package com.crowdin.platform.data.model

data class AuthConfig(
data class AuthConfig @Deprecated(
"Use constructor without organizationName instead. organizationName is now part of the `CrowdinConfig` " +
"and should be provided with this method: `.withOrganizationName(organizationName)`",
replaceWith = ReplaceWith("AuthConfig(clientId, clientSecret, requestAuthDialog)")
) constructor(
val clientId: String,
val clientSecret: String,
@Deprecated("Use CrowdinConfig property instead")
val organizationName: String? = null,
val requestAuthDialog: Boolean = true
)
) {

constructor(clientId: String, clientSecret: String) : this(
clientId = clientId,
clientSecret = clientSecret,
organizationName = null,
requestAuthDialog = true
)

constructor(clientId: String, clientSecret: String, requestAuthDialog: Boolean) : this(
clientId = clientId,
clientSecret = clientSecret,
organizationName = null,
requestAuthDialog = requestAuthDialog
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal class SessionInterceptor(private val session: Session) : Interceptor {

private fun refreshToken(): Boolean {
return try {
session.refreshToken(Crowdin.getAuthConfig())
session.refreshToken(Crowdin.getOrganizationName(), Crowdin.getAuthConfig())
} catch (th: Throwable) {
false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ class SessionInterceptorTest {
sessionInterceptor.intercept(chain)

// Then
verify(session).refreshToken(any())
verify(session).refreshToken(any(), any())
}

@Test
fun intercept_whenTokenExpiredAndRefreshFailed_shouldInvalidate() {
// Given
val session = givenMockSession()
`when`(session.isTokenExpired()).thenReturn(true)
`when`(session.refreshToken(any())).thenReturn(false)
`when`(session.refreshToken(any(), any())).thenReturn(false)
val sessionInterceptor = SessionInterceptor(session)
val chain = givenMockChain()

Expand All @@ -74,7 +74,7 @@ class SessionInterceptorTest {
initCrowdin()
val session = givenMockSession()
`when`(session.isTokenExpired()).thenReturn(false)
`when`(session.refreshToken(any())).thenReturn(false)
`when`(session.refreshToken(any(), any())).thenReturn(false)
val sessionInterceptor = SessionInterceptor(session)
val chain = givenMockChain()
givenFailResponse(chain)
Expand All @@ -83,7 +83,7 @@ class SessionInterceptorTest {
sessionInterceptor.intercept(chain)

// Then
verify(session).refreshToken(any())
verify(session).refreshToken(any(), any())
}

@Test
Expand All @@ -92,7 +92,7 @@ class SessionInterceptorTest {
initCrowdin()
val session = givenMockSession()
`when`(session.isTokenExpired()).thenReturn(false)
`when`(session.refreshToken(any())).thenReturn(false)
`when`(session.refreshToken(any(), any())).thenReturn(false)
val sessionInterceptor = SessionInterceptor(session)
val chain = givenMockChain()
givenFailResponse(chain)
Expand All @@ -110,7 +110,7 @@ class SessionInterceptorTest {
initCrowdin()
val session = givenMockSession()
`when`(session.isTokenExpired()).thenReturn(false)
`when`(session.refreshToken(any())).thenReturn(true)
`when`(session.refreshToken(any(), any())).thenReturn(true)
val sessionInterceptor = SessionInterceptor(session)
val chain = givenMockChain()
givenFailResponse(chain)
Expand Down
6 changes: 3 additions & 3 deletions crowdin/src/test/java/com/crowdin/platform/SessionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class SessionTest {
`when`(mockDataManager.getRefreshToken()).thenReturn(null)

// When
val result = session.refreshToken(mockAuthConfig)
val result = session.refreshToken(null, mockAuthConfig)

// Then
assertThat(result, `is`(false))
Expand All @@ -94,7 +94,7 @@ class SessionTest {
`when`(mockCallResponse.execute()).thenReturn(Response.success(provideAuthResponse()))

// When
session.refreshToken(mockAuthConfig)
session.refreshToken(null, mockAuthConfig)

// Then
verify(mockDataManager).getRefreshToken()
Expand All @@ -112,7 +112,7 @@ class SessionTest {
val expectedAuthInfo = AuthInfo(authResponse)

// When
val result = session.refreshToken(mockAuthConfig)
val result = session.refreshToken(null, mockAuthConfig)

// Then
assertThat(result, `is`(true))
Expand Down
5 changes: 3 additions & 2 deletions example/src/main/java/com/crowdin/platform/example/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class App : Application() {
val clientId = "your_client_id" // "gpY2yC...cx3TYB"
val clientSecret = "your_client_secret" // "Xz95tfedd0A...TabEDx9T"
val organizationName = "your_organization_name" // for Crowdin Enterprise users only
val requestAuthDialog = true // Request authorization dialog `true` by default or `false`

// Set custom locale before SDK initialization.
this.updateLocale(languagePreferences.getLanguageCode())
Expand All @@ -57,10 +58,10 @@ class App : Application() {
AuthConfig(
clientId = clientId,
clientSecret = clientSecret,
organizationName = organizationName,
requestAuthDialog = true
requestAuthDialog = requestAuthDialog
)
)
.withOrganizationName(organizationName) // required for Crowdin Enterprise
.withInitSyncDisabled() // optional
.build()
)
Expand Down
6 changes: 3 additions & 3 deletions website/docs/advanced-features/real-time-preview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ import samplePreviewDisconnectJava from '!!raw-loader!../code-samples/real-time-
| `withDistributionHash` | Distribution Hash | `withDistributionHash("7a0c1...7uo3b")` |
| `withRealTimeUpdates` | Enable Real-Time Preview feature | `withRealTimeUpdates()` |
| `withSourceLanguage` | Source language code in your Crowdin project | `withSourceLanguage("en")` |
| `withAuthConfig` | Crowdin authorization config | `withAuthConfig(AuthConfig("client_id", "client_secret", "organization_name"))` |
| `withAuthConfig` | Crowdin authorization config | `withAuthConfig(AuthConfig("client_id", "client_secret"))` |
| `client_id`, `client_secret` | Crowdin OAuth Client ID and Client Secret | `"gpY2yC...cx3TYB"`, `"Xz95tfedd0A...TabEDx9T"` |
| `organization_name` | An Organization domain name<br/>(for **Crowdin Enterprise users only**) | `"mycompany"` for Crowdin Enterprise or `null` for crowdin.com |
| `request_auth_dialog` | Request authorization dialog | `true` by default or `false` |
| `request_auth_dialog` | Request authorization dialog | `true` by default or `false` |
| `withOrganizationName` | An Organization domain name<br/>(for **Crowdin Enterprise users only**) | `"mycompany"` for Crowdin Enterprise or `null` for crowdin.com |
| `withNetworkType` | Network type to be used for translations download | Acceptable values are:<br/>- `NetworkType.ALL` (default)<br/> - `NetworkType.CELLULAR`<br/>- `NetworkType.WIFI` |
| `withUpdateInterval` | Translations update interval in seconds. The minimum and the default value is 15 minutes. Translations will be updated every defined time interval once per application load | `withUpdateInterval(900)` |
4 changes: 2 additions & 2 deletions website/docs/advanced-features/screenshots.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ import sampleScreenshotHandlerJava from '!!raw-loader!../code-samples/screenshot
| `withDistributionHash` | Distribution Hash | `withDistributionHash("7a0c1...7uo3b")` |
| `withScreenshotEnabled` | Enable Screenshots feature | `withScreenshotEnabled()` |
| `withSourceLanguage` | Source language code in your Crowdin project | `withSourceLanguage("en")` |
| `withAuthConfig` | Crowdin authorization config | `withAuthConfig(AuthConfig("client_id", "client_secret", "organization_name"))` |
| `withAuthConfig` | Crowdin authorization config | `withAuthConfig(AuthConfig("client_id", "client_secret"))` |
| `client_id`, `client_secret` | Crowdin OAuth Client ID and Client Secret | `"gpY2yC...cx3TYB"`, `"Xz95tfedd0A...TabEDx9T"` |
| `organization_name` | An Organization domain name<br/>(for **Crowdin Enterprise users only**) | `"mycompany"` for Crowdin Enterprise or `null` for crowdin.com |
| `request_auth_dialog` | Request authorization dialog | `true` by default or `false` |
| `withOrganizationName` | An Organization domain name<br/>(for **Crowdin Enterprise users only**) | `"mycompany"` for Crowdin Enterprise or `null` for crowdin.com |
| `withNetworkType` | Network type to be used for translations download | Acceptable values are:<br/>- `NetworkType.ALL` (default)<br/> - `NetworkType.CELLULAR`<br/>- `NetworkType.WIFI` |
| `withUpdateInterval` | Translations update interval in seconds. The minimum and the default value is 15 minutes. Translations will be updated every defined time interval once per application load | `withUpdateInterval(900)` |

Expand Down
2 changes: 1 addition & 1 deletion website/docs/code-samples/real-time-preview/setup.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ protected void onCreate(Bundle savedInstanceState) {
.withAuthConfig(new AuthConfig(
client_id,
client_secret,
organization_name,
request_auth_dialog
))
.withOrganizationName(organization_name) // required for Crowdin Enterprise
.withNetworkType(network_type) // optional
.withUpdateInterval(interval_in_seconds) // optional
.build());
Expand Down
2 changes: 1 addition & 1 deletion website/docs/code-samples/real-time-preview/setup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ override fun onCreate() {
.withAuthConfig(AuthConfig(
client_id,
client_secret,
organization_name,
request_auth_dialog
))
.withOrganizationName(organization_name) // required for Crowdin Enterprise
.withNetworkType(network_type) // optional
.withUpdateInterval(interval_in_seconds) // optional
.build())
Expand Down
2 changes: 1 addition & 1 deletion website/docs/code-samples/screenshots/setup.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ protected void onCreate(Bundle savedInstanceState) {
.withAuthConfig(new AuthConfig(
client_id,
client_secret,
organization_name,
request_auth_dialog
))
.withOrganizationName(organization_name) // required for Crowdin Enterprise
.withNetworkType(network_type) // optional
.withUpdateInterval(interval_in_seconds) // optional
.build());
Expand Down
2 changes: 1 addition & 1 deletion website/docs/code-samples/screenshots/setup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ override fun onCreate() {
.withAuthConfig(AuthConfig(
client_id,
client_secret,
organization_name,
request_auth_dialog
))
.withOrganizationName(organization_name) // required for Crowdin Enterprise
.withNetworkType(network_type) // optional
.withUpdateInterval(interval_in_seconds) // optional
.build())
Expand Down
Loading

0 comments on commit 744634b

Please sign in to comment.