@@ -41,6 +41,12 @@ import org.ole.planet.myplanet.utils.AndroidDecrypter
4141import org.ole.planet.myplanet.utils.DispatcherProvider
4242import org.ole.planet.myplanet.utils.JsonUtils
4343import org.ole.planet.myplanet.utils.TimeUtils
44+ import android.util.Base64
45+ import java.io.IOException
46+ import java.util.Date
47+ import org.ole.planet.myplanet.utils.RetryUtils
48+ import org.ole.planet.myplanet.utils.SecurePrefs
49+ import org.ole.planet.myplanet.utils.Utilities
4450import org.ole.planet.myplanet.utils.UrlUtils
4551
4652class UserRepositoryImpl @Inject constructor(
@@ -682,7 +688,7 @@ class UserRepositoryImpl @Inject constructor(
682688
683689 if (userModel != null ) {
684690 try {
685- uploadToShelfService.get(). saveKeyIv(apiInterface, userModel, obj)
691+ saveKeyIv(userModel, obj)
686692 } catch (_: Exception ) { }
687693 Result .success(userModel)
688694 } else {
@@ -694,6 +700,183 @@ class UserRepositoryImpl @Inject constructor(
694700 }
695701 }
696702
703+
704+ override suspend fun changeUserSecurity (model : RealmUser , obj : JsonObject ) {
705+ val table = " userdb-${Utilities .toHex(model.planetCode)} -${Utilities .toHex(model.name)} "
706+ val header = " Basic ${Base64 .encodeToString((" ${obj[" name" ].asString} :${obj[" password" ].asString} " ).toByteArray(), Base64 .NO_WRAP )} "
707+ try {
708+ val response = apiInterface.getJsonObject(header, " ${UrlUtils .getUrl()} /${table} /_security" )
709+ if (response.body() != null ) {
710+ val jsonObject = response.body()
711+ val members = jsonObject?.getAsJsonObject(" members" )
712+ val rolesArray: JsonArray = if (members?.has(" roles" ) == true ) {
713+ members.getAsJsonArray(" roles" )
714+ } else {
715+ JsonArray ()
716+ }
717+ rolesArray.add(" health" )
718+ members?.add(" roles" , rolesArray)
719+ jsonObject?.add(" members" , members)
720+ apiInterface.putDoc(header, " application/json" , " ${UrlUtils .getUrl()} /${table} /_security" , jsonObject)
721+ }
722+ } catch (e: Exception ) {
723+ e.printStackTrace()
724+ }
725+ }
726+
727+ override suspend fun saveKeyIv (model : RealmUser , obj : JsonObject ) {
728+ val table = " userdb-${Utilities .toHex(model.planetCode)} -${Utilities .toHex(model.name)} "
729+ val header = " Basic ${Base64 .encodeToString((" ${obj[" name" ].asString} :${obj[" password" ].asString} " ).toByteArray(), Base64 .NO_WRAP )} "
730+ val ob = JsonObject ()
731+ var keyString = AndroidDecrypter .generateKey()
732+ var iv: String? = AndroidDecrypter .generateIv()
733+
734+ if (! TextUtils .isEmpty(model.iv)) {
735+ iv = model.iv
736+ }
737+ if (! TextUtils .isEmpty(model.key)) {
738+ keyString = model.key
739+ }
740+
741+ ob.addProperty(" key" , keyString)
742+ ob.addProperty(" iv" , iv)
743+ ob.addProperty(" createdOn" , Date ().time)
744+
745+ val maxAttempts = 3
746+ val retryDelayMs = 2000L
747+ val dbUrl = " ${UrlUtils .getUrl()} /$table "
748+
749+ withContext(dispatcherProvider.io) {
750+ try {
751+ apiInterface.putDoc(header, " application/json" , dbUrl, JsonObject ())
752+ } catch (e: Exception ) {
753+ null
754+ }
755+ }
756+
757+ val response = withContext(dispatcherProvider.io) {
758+ RetryUtils .retry(
759+ maxAttempts = maxAttempts,
760+ delayMs = retryDelayMs,
761+ shouldRetry = { resp -> resp == null || ! resp.isSuccessful || resp.body() == null }
762+ ) {
763+ apiInterface.postDoc(header, " application/json" , " ${UrlUtils .getUrl()} /$table " , ob)
764+ }
765+ }
766+
767+ if (response?.isSuccessful == true && response.body() != null ) {
768+ changeUserSecurity(model, obj)
769+
770+ markUserKeyIvSaved(model.id ? : " " , keyString ? : " " , iv)
771+ } else {
772+ throw IOException (" Failed to save key/IV after $maxAttempts attempts" )
773+ }
774+ }
775+
776+ private fun replacedUrl (model : RealmUser ): String {
777+ val url = UrlUtils .getUrl()
778+ val password = SecurePrefs .getPassword(context, settings) ? : " "
779+ val replacedUrl = url.replaceFirst(" [^:]+:[^@]+@" .toRegex(), " ${model.name} :${password} @" )
780+ val protocolIndex = url.indexOf(" ://" )
781+ val protocol = url.substring(0 , protocolIndex)
782+ return " $protocol ://$replacedUrl "
783+ }
784+
785+ override suspend fun checkIfUserExists (header : String , model : RealmUser ): Boolean {
786+ try {
787+ val res = apiInterface.getJsonObject(header, " ${replacedUrl(model)} /_users/org.couchdb.user:${model.name} " )
788+ val exists = res.body() != null
789+ return exists
790+ } catch (e: Exception ) {
791+ e.printStackTrace()
792+ return false
793+ }
794+ }
795+
796+ override suspend fun processUserAfterCreation (model : RealmUser , obj : JsonObject , updateHealthFn : suspend (String , String ) -> Unit ) {
797+ try {
798+ val password = model.password ? : SecurePrefs .getPassword(context, settings) ? : " "
799+ val header = " Basic ${Base64 .encodeToString((" ${model.name} :${password} " ).toByteArray(), Base64 .NO_WRAP )} "
800+ val fetchDataResponse = apiInterface.getJsonObject(header, " ${replacedUrl(model)} /_users/${model._id } " )
801+
802+ if (fetchDataResponse.isSuccessful) {
803+ val passwordScheme = JsonUtils .getString(" password_scheme" , fetchDataResponse.body())
804+ val derivedKey = JsonUtils .getString(" derived_key" , fetchDataResponse.body())
805+ val salt = JsonUtils .getString(" salt" , fetchDataResponse.body())
806+ val iterations = JsonUtils .getString(" iterations" , fetchDataResponse.body())
807+
808+ model.password_scheme = passwordScheme
809+ model.derived_key = derivedKey
810+ model.salt = salt
811+ model.iterations = iterations
812+
813+ updateSecurityData(
814+ model.name ? : " " ,
815+ model._id ,
816+ model._rev ,
817+ derivedKey,
818+ salt,
819+ passwordScheme,
820+ iterations
821+ )
822+
823+ saveKeyIv(model, obj)
824+
825+ updateHealthFn(model.id ? : " " , model._id ? : " " )
826+ }
827+ } catch (e: Exception ) {
828+ e.printStackTrace()
829+ }
830+ }
831+
832+ override suspend fun uploadNewUser (model : RealmUser , updateHealthFn : suspend (String , String ) -> Unit ) {
833+ try {
834+ val obj = model.serialize()
835+ val createResponse = apiInterface.putDoc(null , " application/json" , " ${replacedUrl(model)} /_users/org.couchdb.user:${model.name} " , obj)
836+
837+ if (createResponse.isSuccessful) {
838+ val id = createResponse.body()?.get(" id" )?.asString
839+ val rev = createResponse.body()?.get(" rev" )?.asString
840+ model._id = id
841+ model._rev = rev
842+
843+ // Persist _id and _rev to database
844+ markUserUploaded(model.id ? : " " , id ? : " " , rev ? : " " )
845+
846+ processUserAfterCreation(model, obj, updateHealthFn)
847+ }
848+ } catch (e: Exception ) {
849+ e.printStackTrace()
850+ }
851+ }
852+
853+ override suspend fun updateExistingUser (header : String , model : RealmUser ) {
854+ try {
855+ val latestDocResponse = apiInterface.getJsonObject(header, " ${replacedUrl(model)} /_users/org.couchdb.user:${model.name} " )
856+
857+ if (latestDocResponse.isSuccessful) {
858+ val latestRev = latestDocResponse.body()?.get(" _rev" )?.asString
859+ val obj = model.serialize()
860+ val objMap = obj.entrySet().associate { (key, value) -> key to value }
861+ val mutableObj = mutableMapOf<String , Any >().apply { putAll(objMap) }
862+ latestRev?.let { rev -> mutableObj[" _rev" ] = rev as Any }
863+
864+ val gson = com.google.gson.Gson ()
865+ val jsonElement = gson.toJsonTree(mutableObj)
866+ val jsonObject = jsonElement.asJsonObject
867+
868+ val updateResponse = apiInterface.putDoc(header, " application/json" , " ${replacedUrl(model)} /_users/org.couchdb.user:${model.name} " , jsonObject)
869+
870+ if (updateResponse.isSuccessful) {
871+ val updatedRev = updateResponse.body()?.get(" rev" )?.asString
872+ markUserRevUpdated(model.id ? : " " , updatedRev)
873+ }
874+ }
875+ } catch (e: Exception ) {
876+ e.printStackTrace()
877+ }
878+ }
879+
697880 override suspend fun getActiveUserIdSuspending (): String {
698881 return getUserModelSuspending()?.id ? : " "
699882 }
0 commit comments