Skip to content

Commit

Permalink
Merge pull request #310 from ivpn/feature/device-management
Browse files Browse the repository at this point in the history
Add support for Device Management
  • Loading branch information
jurajhilje authored Feb 12, 2024
2 parents d236229 + ec8de07 commit 5173bb7
Show file tree
Hide file tree
Showing 35 changed files with 1,406 additions and 123 deletions.
12 changes: 12 additions & 0 deletions core/src/main/java/net/ivpn/core/common/Mapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import net.ivpn.core.rest.data.ServersListResponse
import net.ivpn.core.rest.data.model.AntiTracker
import net.ivpn.core.rest.data.model.Port
import net.ivpn.core.rest.data.model.Server
import net.ivpn.core.rest.data.session.SessionErrorResponse
import net.ivpn.core.rest.data.wireguard.ErrorResponse
import java.util.*

Expand Down Expand Up @@ -105,4 +106,15 @@ object Mapper {
null
}
}

@JvmStatic
fun sessionErrorResponseFrom(json: String?): SessionErrorResponse? {
return if (json == null || json.isEmpty()) null else try {
Gson().fromJson(json, SessionErrorResponse::class.java)
} catch (jsonSyntaxException: JsonSyntaxException) {
null
} catch (jsonSyntaxException: IllegalStateException) {
null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class EncryptedUserPreference @Inject constructor(val preference: Preference) {
private const val SESSION_TOKEN = "SESSION_TOKEN"
private const val SESSION_VPN_USERNAME = "SESSION_VPN_USERNAME"
private const val SESSION_VPN_PASSWORD = "SESSION_VPN_PASSWORD"
private const val DEVICE_MANAGEMENT = "DEVICE_MANAGEMENT"
private const val DEVICE_NAME = "DEVICE_NAME"

private const val BLANK_USERNAME = "BLANK_USERNAME"
private const val BLANK_USERNAME_GENERATED_DATE = "BLANK_USERNAME_GENERATED_DATE"
Expand Down Expand Up @@ -93,6 +95,18 @@ class EncryptedUserPreference @Inject constructor(val preference: Preference) {
.apply()
}

fun putDeviceManagement(deviceManagement: Boolean) {
sharedPreferences.edit()
.putBoolean(DEVICE_MANAGEMENT, deviceManagement)
.apply()
}

fun putDeviceName(deviceName: String?) {
sharedPreferences.edit()
.putString(DEVICE_NAME, deviceName)
.apply()
}

fun putCapabilityMultiHop(isAvailable: Boolean) {
sharedPreferences.edit()
.putBoolean(USER_MULTI_HOP, isAvailable)
Expand Down Expand Up @@ -139,6 +153,14 @@ class EncryptedUserPreference @Inject constructor(val preference: Preference) {
return sharedPreferences.getString(SESSION_VPN_PASSWORD, "")
}

fun getDeviceManagement(): Boolean {
return sharedPreferences.getBoolean(DEVICE_MANAGEMENT, false)
}

fun getDeviceName(): String? {
return sharedPreferences.getString(DEVICE_NAME, "")
}

fun getSessionToken(): String {
return sharedPreferences.getString(SESSION_TOKEN, "") ?: ""
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ import net.ivpn.core.rest.Responses
import net.ivpn.core.rest.data.model.ServiceStatus
import net.ivpn.core.rest.data.model.WireGuard
import net.ivpn.core.rest.data.session.*
import net.ivpn.core.rest.data.wireguard.ErrorResponse
import net.ivpn.core.rest.requests.common.Request
import net.ivpn.core.rest.requests.common.RequestWrapper
import net.ivpn.core.v2.login.LoginViewModel
import net.ivpn.core.v2.viewmodel.AccountViewModel
import net.ivpn.core.v2.viewmodel.ViewModelCleaner
import net.ivpn.core.vpn.Protocol
import net.ivpn.core.vpn.ProtocolController
Expand Down Expand Up @@ -125,7 +125,10 @@ class SessionController @Inject constructor(

override fun onError(error: String) {
LOGGER.error("On create session error = $error")
val errorResponse = Mapper.errorResponseFrom(error)
val errorResponse = Mapper.sessionErrorResponseFrom(error)
if (errorResponse != null) {
errorResponse.isAccountNewStyle = AccountViewModel.isNewStyleAccount(body.username)
}
onCreateError(null, errorResponse)
}
})
Expand All @@ -146,6 +149,7 @@ class SessionController @Inject constructor(
if (response.status != null && response.status == Responses.SUCCESS) {
LOGGER.info("Session status response received successfully")
LOGGER.info(response.toString())
userPreference.putDeviceName(response.deviceName)
saveSessionStatus(response.serviceStatus)
onUpdateSuccess()
}
Expand All @@ -158,14 +162,14 @@ class SessionController @Inject constructor(

override fun onError(error: String) {
LOGGER.error("Error while getting account status to see the confirmation$error")
val errorResponse = Mapper.errorResponseFrom(error)
val errorResponse = Mapper.sessionErrorResponseFrom(error)
errorResponse?.let {
if (it.status == Responses.SERVICE_IS_NOT_ACTIVE) {
userPreference.putIsActive(false)
}
if ((it.status == Responses.SESSION_NOT_FOUND)) {
clearSessionData()
onRemoveSuccess()
onDeviceLoggedOut()
}
}
onUpdateError(null, errorResponse)
Expand Down Expand Up @@ -234,7 +238,7 @@ class SessionController @Inject constructor(
}
}

private fun onCreateError(throwable: Throwable?, errorResponse: ErrorResponse?) {
private fun onCreateError(throwable: Throwable?, errorResponse: SessionErrorResponse?) {
for (listener in listeners) {
listener.onCreateError(throwable, errorResponse)
}
Expand All @@ -246,12 +250,18 @@ class SessionController @Inject constructor(
}
}

private fun onUpdateError(throwable: Throwable?, errorResponse: ErrorResponse?) {
private fun onUpdateError(throwable: Throwable?, errorResponse: SessionErrorResponse?) {
for (listener in listeners) {
listener.onUpdateError(throwable, errorResponse)
}
}

private fun onDeviceLoggedOut() {
for (listener in listeners) {
listener.onDeviceLoggedOut()
}
}

fun clearData() {
IVPNApplication.appComponent.provideComponentUtil().resetComponents()
ViewModelCleaner().fullClean()
Expand Down Expand Up @@ -292,6 +302,7 @@ class SessionController @Inject constructor(
userPreference.putSessionToken(response.token)
userPreference.putSessionUsername(response.vpnUsername)
userPreference.putSessionPassword(response.vpnPassword)
userPreference.putDeviceName(response.deviceName)
saveSessionStatus(response.serviceStatus)
}

Expand All @@ -304,6 +315,9 @@ class SessionController @Inject constructor(
userPreference.putCurrentPlan(serviceStatus.currentPlan)
userPreference.putPaymentMethod(serviceStatus.paymentMethod)
userPreference.putIsActive(serviceStatus.isActive)
serviceStatus.deviceManagement?.let {
userPreference.putDeviceManagement(it)
}
if (serviceStatus.capabilities != null) {
userPreference.putIsUserOnPrivateEmailBeta(serviceStatus.capabilities.contains(Responses.PRIVATE_EMAILS))
val multiHopCapabilities = serviceStatus.capabilities.contains(Responses.MULTI_HOP)
Expand Down Expand Up @@ -348,15 +362,11 @@ class SessionController @Inject constructor(

interface SessionListener {
fun onRemoveSuccess()

fun onRemoveError()

fun onCreateSuccess(response: SessionNewResponse)

fun onCreateError(throwable: Throwable?, errorResponse: ErrorResponse?)

fun onCreateError(throwable: Throwable?, errorResponse: SessionErrorResponse?)
fun onUpdateSuccess()

fun onUpdateError(throwable: Throwable?, errorResponse: ErrorResponse?)
fun onUpdateError(throwable: Throwable?, errorResponse: SessionErrorResponse?)
fun onDeviceLoggedOut()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,22 @@ package net.ivpn.core.common.session
*/

import net.ivpn.core.common.session.SessionController.*
import net.ivpn.core.rest.data.session.SessionErrorResponse
import net.ivpn.core.rest.data.session.SessionNewResponse
import net.ivpn.core.rest.data.wireguard.ErrorResponse

open class SessionListenerImpl: SessionListener {
override fun onRemoveSuccess() {
}

override fun onRemoveError() {
}

override fun onCreateSuccess(response: SessionNewResponse) {
}

override fun onCreateError(throwable: Throwable?, errorResponse: ErrorResponse?) {
override fun onCreateError(throwable: Throwable?, errorResponse: SessionErrorResponse?) {
}

override fun onUpdateSuccess() {
}

override fun onUpdateError(throwable: Throwable?, errorResponse: ErrorResponse?) {
override fun onUpdateError(throwable: Throwable?, errorResponse: SessionErrorResponse?) {
}
override fun onDeviceLoggedOut() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public class ServiceStatus {
@SerializedName("capabilities")
@Expose
private List<String> capabilities = null;
@SerializedName("device_management")
@Expose
private Boolean deviceManagement;

public Boolean getIsActive() {
return isActive;
Expand Down Expand Up @@ -118,6 +121,10 @@ public void setCurrentPlan(String currentPlan) {
this.currentPlan = currentPlan;
}

public Boolean getDeviceManagement() {
return deviceManagement;
}

@Override
public String toString() {
return "ServiceStatus{" +
Expand All @@ -128,6 +135,7 @@ public String toString() {
", isRenewable='" + isRenewable + '\'' +
", willAutoRebill='" + willAutoRebill + '\'' +
", isOnFreeTrial='" + isOnFreeTrial + '\'' +
", deviceManagement='" + deviceManagement + '\'' +
", capabilities=" + capabilities +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package net.ivpn.core.rest.data.session;

/*
IVPN Android app
https://github.com/ivpn/android-app
Created by Juraj Hilje.
Copyright (c) 2024 IVPN Limited.
This file is part of the IVPN Android app.
The IVPN Android app is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any later version.
The IVPN Android app is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License
along with the IVPN Android app. If not, see <https://www.gnu.org/licenses/>.
*/

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

class SessionErrorData {

@SerializedName("current_plan")
@Expose
var currentPlan: String = ""

@SerializedName("device_management")
@Expose
var deviceManagement: Boolean = false

@SerializedName("device_management_url")
@Expose
var deviceManagementUrl: String = ""

@SerializedName("limit")
@Expose
var limit: Int = 0

@SerializedName("payment_method")
@Expose
var paymentMethod: String = ""

@SerializedName("upgradable")
@Expose
var upgradable: Boolean = false

@SerializedName("upgrade_to_plan")
@Expose
var upgradeToPlan: String = ""

@SerializedName("upgrade_to_url")
@Expose
var upgradeToUrl: String = ""

override fun toString(): String {
return "SessionErrorData(currentPlan='$currentPlan', deviceManagement=$deviceManagement, deviceManagementUrl='$deviceManagementUrl', limit=$limit, paymentMethod='$paymentMethod', upgradable=$upgradable, upgradeToPlan='$upgradeToPlan', upgradeToUrl='$upgradeToUrl')"
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package net.ivpn.core.rest.data.session;

/*
IVPN Android app
https://github.com/ivpn/android-app
Created by Juraj Hilje.
Copyright (c) 2024 IVPN Limited.
This file is part of the IVPN Android app.
The IVPN Android app is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any later version.
The IVPN Android app is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License
along with the IVPN Android app. If not, see <https://www.gnu.org/licenses/>.
*/

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

class SessionErrorResponse {

@SerializedName("status")
@Expose
var status: Int = 0

@SerializedName("message")
@Expose
var message: String = ""

@SerializedName("captcha_id")
@Expose
val captchaId: String? = null

@SerializedName("captcha_image")
@Expose
val captchaImage: String? = null

@SerializedName("data")
@Expose
var data:SessionErrorData? = null

var isAccountNewStyle: Boolean = true

override fun toString(): String {
return "SessionErrorResponse(status=$status, message='$message', captchaId=$captchaId, captchaImage=$captchaImage, data=$data, isAccountNewStyle=$isAccountNewStyle)"
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public class SessionNewResponse {
@SerializedName("vpn_password")
@Expose
private String vpnPassword;
@SerializedName("device_name")
@Expose
private String deviceName;
@SerializedName("service_status")
@Expose
private ServiceStatus serviceStatus;
Expand Down Expand Up @@ -76,6 +79,10 @@ public String getVpnPassword() {
return vpnPassword;
}

public String getDeviceName() {
return deviceName;
}

public void setVpnPassword(String vpnPassword) {
this.vpnPassword = vpnPassword;
}
Expand All @@ -101,6 +108,7 @@ public String toString() {
return "SessionNewResponse{" +
"status=" + status +
", serviceStatus=" + serviceStatus +
", deviceName=" + deviceName +
", wireGuard=" + wireGuard +
'}';
}
Expand Down
Loading

0 comments on commit 5173bb7

Please sign in to comment.