mirror of
https://github.com/etesync/android
synced 2024-11-22 16:08:13 +00:00
Move to the external journalmanager module (moved the code there)
This commit is contained in:
parent
b70e8903c5
commit
ceead4815b
@ -133,6 +133,7 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation "org.jetbrains.anko:anko-commons:0.10.4"
|
implementation "org.jetbrains.anko:anko-commons:0.10.4"
|
||||||
|
|
||||||
|
implementation "com.etesync:journalmanager:1.0.1"
|
||||||
|
|
||||||
def acraVersion = '5.3.0'
|
def acraVersion = '5.3.0'
|
||||||
implementation "ch.acra:acra-mail:$acraVersion"
|
implementation "ch.acra:acra-mail:$acraVersion"
|
||||||
@ -157,10 +158,6 @@ dependencies {
|
|||||||
kapt "io.requery:requery-processor:$requeryVersion"
|
kapt "io.requery:requery-processor:$requeryVersion"
|
||||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||||
|
|
||||||
def spongyCastleVersion = "1.54.0.0"
|
|
||||||
implementation "com.madgag.spongycastle:core:$spongyCastleVersion"
|
|
||||||
implementation "com.madgag.spongycastle:prov:$spongyCastleVersion"
|
|
||||||
|
|
||||||
def okhttp3Version = "3.12.1"
|
def okhttp3Version = "3.12.1"
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp3Version"
|
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp3Version"
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import at.bitfire.vcard4android.ContactsStorageException
|
import at.bitfire.vcard4android.ContactsStorageException
|
||||||
import at.bitfire.vcard4android.GroupMethod
|
import at.bitfire.vcard4android.GroupMethod
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.utils.Base64
|
import com.etesync.syncadapter.utils.Base64
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.GsonHelper
|
|
||||||
import com.etesync.syncadapter.log.Logger
|
|
||||||
import okhttp3.*
|
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.HttpURLConnection
|
|
||||||
import java.util.logging.Level
|
|
||||||
import javax.net.ssl.SSLHandshakeException
|
|
||||||
import javax.net.ssl.SSLProtocolException
|
|
||||||
|
|
||||||
abstract class BaseManager {
|
|
||||||
|
|
||||||
protected var remote: HttpUrl? = null
|
|
||||||
protected var client: OkHttpClient? = null
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun newCall(request: Request): Response {
|
|
||||||
val response: Response
|
|
||||||
try {
|
|
||||||
Logger.log.fine("Making request for ${request.url()}")
|
|
||||||
response = client!!.newCall(request).execute()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
if (e is SSLProtocolException) {
|
|
||||||
throw e
|
|
||||||
} else if (e is SSLHandshakeException && e.cause is SSLProtocolException) {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
Logger.log.log(Level.SEVERE, "Failed while connecting to server", e)
|
|
||||||
throw Exceptions.ServiceUnavailableException("[" + e.javaClass.name + "] " + e.localizedMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
val apiError = if (response.header("Content-Type", "application/json")!!.startsWith("application/json"))
|
|
||||||
GsonHelper.gson.fromJson(response.body()!!.charStream(), ApiError::class.java)
|
|
||||||
else
|
|
||||||
ApiError(code="got_html", detail="Got HTML while expecting JSON")
|
|
||||||
|
|
||||||
|
|
||||||
when (response.code()) {
|
|
||||||
HttpURLConnection.HTTP_BAD_GATEWAY -> throw Exceptions.BadGatewayException(response, "Bad gateway: most likely a server restart")
|
|
||||||
HttpURLConnection.HTTP_UNAVAILABLE -> throw Exceptions.ServiceUnavailableException(response, "Service unavailable")
|
|
||||||
HttpURLConnection.HTTP_UNAUTHORIZED -> throw Exceptions.UnauthorizedException(response, "Unauthorized auth token")
|
|
||||||
HttpURLConnection.HTTP_CONFLICT -> throw Exceptions.ConflictException(response, "Http conflict")
|
|
||||||
HttpURLConnection.HTTP_FORBIDDEN -> {
|
|
||||||
if (apiError.code == "service_inactive") {
|
|
||||||
throw Exceptions.UserInactiveException(response, apiError.detail)
|
|
||||||
} else if (apiError.code == "associate_not_allowed") {
|
|
||||||
throw Exceptions.AssociateNotAllowedException(response, apiError.detail)
|
|
||||||
} else if (apiError.code == "journal_owner_inactive") {
|
|
||||||
throw Exceptions.ReadOnlyException(response, apiError.detail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}// Fall through. We want to always throw when unsuccessful.
|
|
||||||
|
|
||||||
throw Exceptions.HttpException(response, apiError.detail)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class ApiError(
|
|
||||||
var detail: String? = null,
|
|
||||||
var code: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
open class Base {
|
|
||||||
var content: ByteArray? = null
|
|
||||||
var uid: String? = null
|
|
||||||
|
|
||||||
fun getContent(crypto: Crypto.CryptoManager): String {
|
|
||||||
return String(crypto.decrypt(content!!)!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setContent(crypto: Crypto.CryptoManager, content: String) {
|
|
||||||
this.content = crypto.encrypt(content.toByteArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun calculateHmac(crypto: Crypto.CryptoManager, uuid: String?): ByteArray {
|
|
||||||
val hashContent = ByteArrayOutputStream()
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (uuid != null) {
|
|
||||||
hashContent.write(uuid.toByteArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
hashContent.write(content!!)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
// Can never happen, but just in case, return a bad hmac
|
|
||||||
return "DEADBEEFDEADBEEFDEADBEEFDEADBEEF".toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
return crypto.hmac(hashContent.toByteArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
protected constructor() {}
|
|
||||||
|
|
||||||
constructor(crypto: Crypto.CryptoManager, content: String, uid: String) {
|
|
||||||
setContent(crypto, content)
|
|
||||||
this.uid = uid
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return javaClass.simpleName + "<" + uid + ">"
|
|
||||||
}
|
|
||||||
|
|
||||||
internal open fun toJson(): String {
|
|
||||||
return GsonHelper.gson.toJson(this, javaClass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val JSON = MediaType.parse("application/json; charset=utf-8")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
class Constants {
|
|
||||||
companion object {
|
|
||||||
@JvmField
|
|
||||||
val CURRENT_VERSION = 2
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,262 +0,0 @@
|
|||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.journalmanager.util.ByteUtil
|
|
||||||
import com.etesync.syncadapter.log.Logger
|
|
||||||
import com.etesync.syncadapter.utils.Base64
|
|
||||||
import org.apache.commons.lang3.ArrayUtils
|
|
||||||
import org.spongycastle.crypto.AsymmetricBlockCipher
|
|
||||||
import org.spongycastle.crypto.BufferedBlockCipher
|
|
||||||
import org.spongycastle.crypto.InvalidCipherTextException
|
|
||||||
import org.spongycastle.crypto.digests.SHA256Digest
|
|
||||||
import org.spongycastle.crypto.encodings.OAEPEncoding
|
|
||||||
import org.spongycastle.crypto.engines.AESEngine
|
|
||||||
import org.spongycastle.crypto.engines.RSAEngine
|
|
||||||
import org.spongycastle.crypto.generators.RSAKeyPairGenerator
|
|
||||||
import org.spongycastle.crypto.generators.SCrypt
|
|
||||||
import org.spongycastle.crypto.macs.HMac
|
|
||||||
import org.spongycastle.crypto.modes.CBCBlockCipher
|
|
||||||
import org.spongycastle.crypto.paddings.PKCS7Padding
|
|
||||||
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher
|
|
||||||
import org.spongycastle.crypto.params.KeyParameter
|
|
||||||
import org.spongycastle.crypto.params.ParametersWithIV
|
|
||||||
import org.spongycastle.crypto.params.RSAKeyGenerationParameters
|
|
||||||
import org.spongycastle.crypto.util.PrivateKeyFactory
|
|
||||||
import org.spongycastle.crypto.util.PrivateKeyInfoFactory
|
|
||||||
import org.spongycastle.crypto.util.PublicKeyFactory
|
|
||||||
import org.spongycastle.crypto.util.SubjectPublicKeyInfoFactory
|
|
||||||
import org.spongycastle.util.encoders.Hex
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.Serializable
|
|
||||||
import java.math.BigInteger
|
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
object Crypto {
|
|
||||||
@JvmStatic
|
|
||||||
fun deriveKey(salt: String, password: String): String {
|
|
||||||
val keySize = 190
|
|
||||||
|
|
||||||
return Base64.encodeToString(SCrypt.generate(password.toByteArray(), salt.toByteArray(), 16384, 8, 1, keySize), Base64.NO_WRAP)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun generateKeyPair(): AsymmetricKeyPair? {
|
|
||||||
val keyPairGenerator = RSAKeyPairGenerator()
|
|
||||||
keyPairGenerator.init(RSAKeyGenerationParameters(BigInteger.valueOf(65537), SecureRandom(), 3072, 160))
|
|
||||||
val keyPair = keyPairGenerator.generateKeyPair()
|
|
||||||
try {
|
|
||||||
val privateKeyInfo = PrivateKeyInfoFactory.createPrivateKeyInfo(keyPair.private)
|
|
||||||
val publicKeyInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(keyPair.public)
|
|
||||||
return AsymmetricKeyPair(privateKeyInfo.encoded, publicKeyInfo.encoded)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
class AsymmetricKeyPair(val privateKey: ByteArray, val publicKey: ByteArray) : Serializable
|
|
||||||
|
|
||||||
class AsymmetricCryptoManager(private val keyPair: AsymmetricKeyPair) {
|
|
||||||
|
|
||||||
fun encrypt(pubkey: ByteArray, content: ByteArray?): ByteArray? {
|
|
||||||
var cipher: AsymmetricBlockCipher = RSAEngine()
|
|
||||||
cipher = OAEPEncoding(cipher)
|
|
||||||
try {
|
|
||||||
cipher.init(true, PublicKeyFactory.createKey(pubkey))
|
|
||||||
return cipher.processBlock(content, 0, content!!.size)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
} catch (e: InvalidCipherTextException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
Logger.log.severe("Invalid ciphertext: " + Base64.encodeToString(content, Base64.NO_WRAP))
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun decrypt(cipherText: ByteArray): ByteArray? {
|
|
||||||
var cipher: AsymmetricBlockCipher = RSAEngine()
|
|
||||||
cipher = OAEPEncoding(cipher)
|
|
||||||
try {
|
|
||||||
cipher.init(false, PrivateKeyFactory.createKey(keyPair.privateKey))
|
|
||||||
return cipher.processBlock(cipherText, 0, cipherText.size)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
} catch (e: InvalidCipherTextException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
Logger.log.severe("Invalid ciphertext: " + Base64.encodeToString(cipherText, Base64.NO_WRAP))
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun getKeyFingerprint(pubkey: ByteArray): ByteArray {
|
|
||||||
return sha256(pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getPrettyKeyFingerprint(pubkey: ByteArray): String {
|
|
||||||
val fingerprint = Crypto.AsymmetricCryptoManager.getKeyFingerprint(pubkey)
|
|
||||||
val spacing = " "
|
|
||||||
val ret = getEncodedChunk(fingerprint, 0) + spacing +
|
|
||||||
getEncodedChunk(fingerprint, 4) + spacing +
|
|
||||||
getEncodedChunk(fingerprint, 8) + spacing +
|
|
||||||
getEncodedChunk(fingerprint, 12) + "\n" +
|
|
||||||
getEncodedChunk(fingerprint, 16) + spacing +
|
|
||||||
getEncodedChunk(fingerprint, 20) + spacing +
|
|
||||||
getEncodedChunk(fingerprint, 24) + spacing +
|
|
||||||
getEncodedChunk(fingerprint, 28)
|
|
||||||
return ret.trim { it <= ' ' }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getEncodedChunk(hash: ByteArray, offset: Int): String {
|
|
||||||
val chunk = ByteUtil.byteArray4ToLong(hash, offset) % 100000
|
|
||||||
return String.format(Locale.getDefault(), "%05d", chunk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CryptoManager {
|
|
||||||
val version: Byte
|
|
||||||
private var cipherKey: ByteArray? = null
|
|
||||||
private var hmacKey: ByteArray? = null
|
|
||||||
private var derivedKey: ByteArray? = null
|
|
||||||
|
|
||||||
private val random: SecureRandom
|
|
||||||
get() = SecureRandom()
|
|
||||||
|
|
||||||
private fun setDerivedKey(derivedKey: ByteArray?) {
|
|
||||||
cipherKey = hmac256("aes".toByteArray(), derivedKey)
|
|
||||||
hmacKey = hmac256("hmac".toByteArray(), derivedKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(version: Int, keyPair: AsymmetricKeyPair, encryptedKey: ByteArray) {
|
|
||||||
val cryptoManager = Crypto.AsymmetricCryptoManager(keyPair)
|
|
||||||
derivedKey = cryptoManager.decrypt(encryptedKey)
|
|
||||||
|
|
||||||
this.version = version.toByte()
|
|
||||||
setDerivedKey(derivedKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.IntegrityException::class, Exceptions.VersionTooNewException::class)
|
|
||||||
constructor(version: Int, keyBase64: String, salt: String) {
|
|
||||||
if (version > java.lang.Byte.MAX_VALUE) {
|
|
||||||
throw Exceptions.IntegrityException("Version is out of range.")
|
|
||||||
} else if (version > Constants.CURRENT_VERSION) {
|
|
||||||
throw Exceptions.VersionTooNewException("Version to new: " + version.toString())
|
|
||||||
} else if (version == 1) {
|
|
||||||
derivedKey = Base64.decode(keyBase64, Base64.NO_WRAP)
|
|
||||||
} else {
|
|
||||||
derivedKey = hmac256(salt.toByteArray(), Base64.decode(keyBase64, Base64.NO_WRAP))
|
|
||||||
}
|
|
||||||
|
|
||||||
this.version = version.toByte()
|
|
||||||
setDerivedKey(derivedKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCipher(iv: ByteArray, encrypt: Boolean): BufferedBlockCipher {
|
|
||||||
val key = KeyParameter(cipherKey!!)
|
|
||||||
val params = ParametersWithIV(key, iv)
|
|
||||||
|
|
||||||
val padding = PKCS7Padding()
|
|
||||||
val cipher = PaddedBufferedBlockCipher(
|
|
||||||
CBCBlockCipher(AESEngine()), padding)
|
|
||||||
cipher.reset()
|
|
||||||
cipher.init(encrypt, params)
|
|
||||||
|
|
||||||
return cipher
|
|
||||||
}
|
|
||||||
|
|
||||||
fun decrypt(_data: ByteArray): ByteArray? {
|
|
||||||
val iv = Arrays.copyOfRange(_data, 0, blockSize)
|
|
||||||
val data = Arrays.copyOfRange(_data, blockSize, _data.size)
|
|
||||||
|
|
||||||
val cipher = getCipher(iv, false)
|
|
||||||
|
|
||||||
val buf = ByteArray(cipher.getOutputSize(data.size))
|
|
||||||
var len = cipher.processBytes(data, 0, data.size, buf, 0)
|
|
||||||
try {
|
|
||||||
len += cipher.doFinal(buf, len)
|
|
||||||
} catch (e: InvalidCipherTextException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
Logger.log.severe("Invalid ciphertext: " + Base64.encodeToString(_data, Base64.NO_WRAP))
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove padding
|
|
||||||
val out = ByteArray(len)
|
|
||||||
System.arraycopy(buf, 0, out, 0, len)
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
fun encrypt(data: ByteArray): ByteArray? {
|
|
||||||
val iv = ByteArray(blockSize)
|
|
||||||
random.nextBytes(iv)
|
|
||||||
|
|
||||||
val cipher = getCipher(iv, true)
|
|
||||||
|
|
||||||
val buf = ByteArray(cipher.getOutputSize(data.size) + blockSize)
|
|
||||||
System.arraycopy(iv, 0, buf, 0, iv.size)
|
|
||||||
val len = iv.size + cipher.processBytes(data, 0, data.size, buf, iv.size)
|
|
||||||
try {
|
|
||||||
cipher.doFinal(buf, len)
|
|
||||||
} catch (e: InvalidCipherTextException) {
|
|
||||||
Logger.log.severe("Invalid ciphertext: " + Base64.encodeToString(data, Base64.NO_WRAP))
|
|
||||||
e.printStackTrace()
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hmac(data: ByteArray): ByteArray {
|
|
||||||
return if (version.toInt() == 1) {
|
|
||||||
hmac256(hmacKey, data)
|
|
||||||
} else {
|
|
||||||
// Starting from version 2 we hmac the version too.
|
|
||||||
hmac256(hmacKey, ArrayUtils.add(data, version))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getEncryptedKey(keyPair: AsymmetricKeyPair, publicKey: ByteArray): ByteArray? {
|
|
||||||
val cryptoManager = AsymmetricCryptoManager(keyPair)
|
|
||||||
return cryptoManager.encrypt(publicKey, derivedKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val HMAC_SIZE = 256 / 8 // hmac256 in bytes
|
|
||||||
|
|
||||||
private val blockSize = 16 // AES's block size in bytes
|
|
||||||
|
|
||||||
private fun hmac256(keyByte: ByteArray?, data: ByteArray?): ByteArray {
|
|
||||||
val hmac = HMac(SHA256Digest())
|
|
||||||
val key = KeyParameter(keyByte!!)
|
|
||||||
val ret = ByteArray(hmac.macSize)
|
|
||||||
hmac.init(key)
|
|
||||||
hmac.update(data, 0, data!!.size)
|
|
||||||
hmac.doFinal(ret, 0)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun sha256(base: String): String {
|
|
||||||
return toHex(sha256(base.toByteArray()))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sha256(base: ByteArray): ByteArray {
|
|
||||||
val digest = SHA256Digest()
|
|
||||||
digest.update(base, 0, base.size)
|
|
||||||
val ret = ByteArray(digest.digestSize)
|
|
||||||
digest.doFinal(ret, 0)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun toHex(bytes: ByteArray): String {
|
|
||||||
return Hex.toHexString(bytes).toLowerCase()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,141 +0,0 @@
|
|||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
import at.bitfire.cert4android.Constants
|
|
||||||
import okhttp3.Response
|
|
||||||
import okio.Buffer
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.Serializable
|
|
||||||
import java.security.GeneralSecurityException
|
|
||||||
|
|
||||||
class Exceptions {
|
|
||||||
class AssociateNotAllowedException(response: Response, message: String?) : HttpException(response, message)
|
|
||||||
|
|
||||||
class ConflictException(response: Response, message: String?) : IgnorableHttpException(response, message ?: "Conflict exception")
|
|
||||||
|
|
||||||
class ReadOnlyException(response: Response, message: String?) : HttpException(response, message)
|
|
||||||
|
|
||||||
class UnauthorizedException(response: Response, message: String?) : HttpException(response, message)
|
|
||||||
|
|
||||||
class UserInactiveException(response: Response, message: String?) : HttpException(response, message)
|
|
||||||
|
|
||||||
class BadGatewayException(response: Response, message: String) : IgnorableHttpException(response, message)
|
|
||||||
|
|
||||||
class ServiceUnavailableException : IgnorableHttpException {
|
|
||||||
var retryAfter: Long = 0
|
|
||||||
|
|
||||||
constructor(message: String) : super(message) {
|
|
||||||
this.retryAfter = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(response: Response, message: String) : super(response, message) {
|
|
||||||
this.retryAfter = java.lang.Long.valueOf(response.header("Retry-After", "0")!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IntegrityException(message: String) : GeneralSecurityException(message)
|
|
||||||
|
|
||||||
open class IgnorableHttpException : HttpException {
|
|
||||||
constructor(message: String) : super(message)
|
|
||||||
constructor(response: Response, message: String) : super(response, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
open class GenericCryptoException(message: String) : Exception(message)
|
|
||||||
|
|
||||||
class VersionTooNewException(message: String) : GenericCryptoException(message)
|
|
||||||
|
|
||||||
open class HttpException : Exception, Serializable {
|
|
||||||
internal val status: Int
|
|
||||||
override val message: String
|
|
||||||
|
|
||||||
val request: String?
|
|
||||||
val response: String?
|
|
||||||
|
|
||||||
constructor(message: String) : super(message) {
|
|
||||||
this.message = message
|
|
||||||
|
|
||||||
this.status = -1
|
|
||||||
this.response = null
|
|
||||||
this.request = this.response
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(status: Int, message: String) : super(status.toString() + " " + message) {
|
|
||||||
this.status = status
|
|
||||||
this.message = message
|
|
||||||
|
|
||||||
response = null
|
|
||||||
request = response
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmOverloads constructor(response: Response, custom_message: String? = null) : super(response.code().toString() + " " + response.message()) {
|
|
||||||
|
|
||||||
status = response.code()
|
|
||||||
message = custom_message ?: response.message()
|
|
||||||
|
|
||||||
/* As we don't know the media type and character set of request and response body,
|
|
||||||
only printable ASCII characters will be shown in clear text. Other octets will
|
|
||||||
be shown as "[xx]" where xx is the hex value of the octet.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// format request
|
|
||||||
val request = response.request()
|
|
||||||
var formatted = StringBuilder()
|
|
||||||
formatted.append(request.method()).append(" ").append(request.url().encodedPath()).append("\n")
|
|
||||||
var headers = request.headers()
|
|
||||||
for (name in headers.names()) {
|
|
||||||
for (value in headers.values(name)) {
|
|
||||||
/* Redact authorization token. */
|
|
||||||
if (name == "Authorization") {
|
|
||||||
formatted.append(name).append(": ").append("XXXXXX").append("\n")
|
|
||||||
} else {
|
|
||||||
formatted.append(name).append(": ").append(value).append("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (request.body() != null)
|
|
||||||
try {
|
|
||||||
formatted.append("\n")
|
|
||||||
val buffer = Buffer()
|
|
||||||
request.body()!!.writeTo(buffer)
|
|
||||||
while (!buffer.exhausted())
|
|
||||||
appendByte(formatted, buffer.readByte())
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Constants.log.warning("Couldn't read request body")
|
|
||||||
}
|
|
||||||
|
|
||||||
this.request = formatted.toString()
|
|
||||||
|
|
||||||
// format response
|
|
||||||
formatted = StringBuilder()
|
|
||||||
formatted.append(response.protocol()).append(" ").append(response.code()).append(" ").append(message).append("\n")
|
|
||||||
headers = response.headers()
|
|
||||||
for (name in headers.names())
|
|
||||||
for (value in headers.values(name))
|
|
||||||
formatted.append(name).append(": ").append(value).append("\n")
|
|
||||||
if (response.body() != null) {
|
|
||||||
val body = response.body()
|
|
||||||
try {
|
|
||||||
formatted.append("\n")
|
|
||||||
for (b in body!!.bytes())
|
|
||||||
appendByte(formatted, b)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Constants.log.warning("Couldn't read response body")
|
|
||||||
}
|
|
||||||
|
|
||||||
body!!.close()
|
|
||||||
}
|
|
||||||
this.response = formatted.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun appendByte(formatted: StringBuilder, b: Byte) {
|
|
||||||
if (b == '\r'.toByte())
|
|
||||||
formatted.append("[CR]")
|
|
||||||
else if (b == '\n'.toByte())
|
|
||||||
formatted.append("[LF]\n")
|
|
||||||
else if (b >= 0x20 && b <= 0x7E)
|
|
||||||
// printable ASCII
|
|
||||||
formatted.append(b.toChar())
|
|
||||||
else
|
|
||||||
formatted.append("[" + Integer.toHexString(b.toInt() and 0xff) + "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.GsonHelper
|
|
||||||
import okhttp3.*
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.HttpURLConnection
|
|
||||||
|
|
||||||
class JournalAuthenticator(private val client: OkHttpClient, private val remote: HttpUrl) {
|
|
||||||
private inner class AuthResponse private constructor() {
|
|
||||||
val token: String? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class, IOException::class)
|
|
||||||
fun getAuthToken(username: String, password: String): String? {
|
|
||||||
val remote = remote.newBuilder()
|
|
||||||
.addPathSegments("api-token-auth")
|
|
||||||
.addPathSegment("")
|
|
||||||
.build()
|
|
||||||
val formBuilder = FormBody.Builder()
|
|
||||||
.add("username", username)
|
|
||||||
.add("password", password)
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.post(formBuilder.build())
|
|
||||||
.url(remote)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val response = client.newCall(request).execute()
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
return GsonHelper.gson.fromJson(response.body()!!.charStream(), AuthResponse::class.java).token
|
|
||||||
} else if (response.code() == HttpURLConnection.HTTP_BAD_REQUEST) {
|
|
||||||
throw Exceptions.UnauthorizedException(response, "Username or password incorrect")
|
|
||||||
} else {
|
|
||||||
throw Exceptions.HttpException(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invalidateAuthToken(authToken: String) {
|
|
||||||
val remote = remote.newBuilder()
|
|
||||||
.addPathSegments("api/logout")
|
|
||||||
.addPathSegment("")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val body = RequestBody.create(null, byteArrayOf())
|
|
||||||
val request = Request.Builder()
|
|
||||||
.post(body)
|
|
||||||
.url(remote)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val response = client.newCall(request).execute()
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
when (response.code()) {
|
|
||||||
HttpURLConnection.HTTP_BAD_GATEWAY -> throw Exceptions.BadGatewayException(response, "Bad gateway: most likely a server restart")
|
|
||||||
HttpURLConnection.HTTP_UNAVAILABLE -> throw Exceptions.ServiceUnavailableException(response, "Service unavailable")
|
|
||||||
HttpURLConnection.HTTP_UNAUTHORIZED -> throw Exceptions.UnauthorizedException(response, "Unauthorized auth token")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.GsonHelper
|
|
||||||
import com.etesync.syncadapter.log.Logger
|
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody
|
|
||||||
|
|
||||||
class JournalEntryManager(httpClient: OkHttpClient, remote: HttpUrl, val uid: String) : BaseManager() {
|
|
||||||
|
|
||||||
init {
|
|
||||||
this.remote = remote.newBuilder()
|
|
||||||
.addPathSegments("api/v1/journals")
|
|
||||||
.addPathSegments(uid)
|
|
||||||
.addPathSegment("entries")
|
|
||||||
.addPathSegment("")
|
|
||||||
.build()
|
|
||||||
Logger.log.info("Created for: " + this.remote!!.toString())
|
|
||||||
|
|
||||||
this.client = httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class, Exceptions.IntegrityException::class)
|
|
||||||
fun list(crypto: Crypto.CryptoManager, last: String?, limit: Int): List<Entry> {
|
|
||||||
var previousEntry: Entry? = null
|
|
||||||
val urlBuilder = this.remote!!.newBuilder()
|
|
||||||
if (last != null) {
|
|
||||||
urlBuilder.addQueryParameter("last", last)
|
|
||||||
previousEntry = Entry.getFakeWithUid(last)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (limit > 0) {
|
|
||||||
urlBuilder.addQueryParameter("limit", limit.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
val remote = urlBuilder.build()
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.get()
|
|
||||||
.url(remote)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val response = newCall(request)
|
|
||||||
val body = response.body()
|
|
||||||
val ret = GsonHelper.gson.fromJson<List<Entry>>(body!!.charStream(), entryType)
|
|
||||||
|
|
||||||
for (entry in ret) {
|
|
||||||
entry.verify(crypto, previousEntry)
|
|
||||||
previousEntry = entry
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun create(entries: List<Entry>, last: String?) {
|
|
||||||
val urlBuilder = this.remote!!.newBuilder()
|
|
||||||
if (last != null) {
|
|
||||||
urlBuilder.addQueryParameter("last", last)
|
|
||||||
}
|
|
||||||
|
|
||||||
val remote = urlBuilder.build()
|
|
||||||
|
|
||||||
val body = RequestBody.create(BaseManager.JSON, GsonHelper.gson.toJson(entries, entryType))
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.post(body)
|
|
||||||
.url(remote)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
newCall(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Entry : BaseManager.Base() {
|
|
||||||
|
|
||||||
fun update(crypto: Crypto.CryptoManager, content: String, previous: Entry?) {
|
|
||||||
setContent(crypto, content)
|
|
||||||
uid = calculateHmac(crypto, previous)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.IntegrityException::class)
|
|
||||||
internal fun verify(crypto: Crypto.CryptoManager, previous: Entry?) {
|
|
||||||
val correctHash = calculateHmac(crypto, previous)
|
|
||||||
if (uid != correctHash) {
|
|
||||||
throw Exceptions.IntegrityException("Bad HMAC. $uid != $correctHash")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateHmac(crypto: Crypto.CryptoManager, previous: Entry?): String {
|
|
||||||
var uuid: String? = null
|
|
||||||
if (previous != null) {
|
|
||||||
uuid = previous.uid
|
|
||||||
}
|
|
||||||
|
|
||||||
return Crypto.toHex(calculateHmac(crypto, uuid))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun getFakeWithUid(uid: String): Entry {
|
|
||||||
val ret = Entry()
|
|
||||||
ret.uid = uid
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val entryType = object : TypeToken<List<Entry>>() {
|
|
||||||
|
|
||||||
}.type
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,220 +0,0 @@
|
|||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.GsonHelper
|
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto.CryptoManager.Companion.HMAC_SIZE
|
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto.sha256
|
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto.toHex
|
|
||||||
import com.etesync.syncadapter.log.Logger
|
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody
|
|
||||||
import org.spongycastle.util.Arrays
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class JournalManager(httpClient: OkHttpClient, remote: HttpUrl) : BaseManager() {
|
|
||||||
init {
|
|
||||||
this.remote = remote.newBuilder()
|
|
||||||
.addPathSegments("api/v1/journals")
|
|
||||||
.addPathSegment("")
|
|
||||||
.build()
|
|
||||||
Logger.log.info("Created for: " + this.remote!!.toString())
|
|
||||||
|
|
||||||
this.client = httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun list(): List<Journal> {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.get()
|
|
||||||
.url(remote!!)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val response = newCall(request)
|
|
||||||
val body = response.body()
|
|
||||||
val ret = GsonHelper.gson.fromJson<List<Journal>>(body!!.charStream(), journalType)
|
|
||||||
|
|
||||||
for (journal in ret) {
|
|
||||||
journal.processFromJson()
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun delete(journal: Journal) {
|
|
||||||
val remote = this.remote!!.resolve(journal.uid!! + "/")
|
|
||||||
val request = Request.Builder()
|
|
||||||
.delete()
|
|
||||||
.url(remote!!)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
newCall(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun create(journal: Journal) {
|
|
||||||
val body = RequestBody.create(BaseManager.JSON, journal.toJson())
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.post(body)
|
|
||||||
.url(remote!!)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
newCall(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun update(journal: Journal) {
|
|
||||||
val remote = this.remote!!.resolve(journal.uid!! + "/")
|
|
||||||
val body = RequestBody.create(BaseManager.JSON, journal.toJson())
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.put(body)
|
|
||||||
.url(remote!!)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
newCall(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getMemberRemote(journal: Journal, user: String?): HttpUrl {
|
|
||||||
val bulider = this.remote!!.newBuilder()
|
|
||||||
bulider.addPathSegment(journal.uid!!)
|
|
||||||
.addPathSegment("members")
|
|
||||||
if (user != null) {
|
|
||||||
bulider.addPathSegment(user)
|
|
||||||
}
|
|
||||||
bulider.addPathSegment("")
|
|
||||||
return bulider.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class, Exceptions.IntegrityException::class, Exceptions.GenericCryptoException::class)
|
|
||||||
fun listMembers(journal: Journal): List<Member> {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.get()
|
|
||||||
.url(getMemberRemote(journal, null))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val response = newCall(request)
|
|
||||||
val body = response.body()
|
|
||||||
return GsonHelper.gson.fromJson(body!!.charStream(), memberType)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun deleteMember(journal: Journal, member: Member) {
|
|
||||||
val body = RequestBody.create(BaseManager.JSON, member.toJson())
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.delete(body)
|
|
||||||
.url(getMemberRemote(journal, member.user))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
newCall(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun addMember(journal: Journal, member: Member) {
|
|
||||||
val body = RequestBody.create(BaseManager.JSON, member.toJson())
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.post(body)
|
|
||||||
.url(getMemberRemote(journal, null))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
newCall(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Journal : BaseManager.Base {
|
|
||||||
val owner: String? = null
|
|
||||||
val key: ByteArray? = null
|
|
||||||
var version = -1
|
|
||||||
val readOnly = false
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
private var hmac: ByteArray? = null
|
|
||||||
|
|
||||||
private constructor() : super() {}
|
|
||||||
|
|
||||||
constructor(crypto: Crypto.CryptoManager, content: String, uid: String) : super(crypto, content, uid) {
|
|
||||||
hmac = calculateHmac(crypto)
|
|
||||||
version = crypto.version.toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun processFromJson() {
|
|
||||||
hmac = Arrays.copyOfRange(content!!, 0, HMAC_SIZE)
|
|
||||||
content = Arrays.copyOfRange(content!!, HMAC_SIZE, content!!.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.IntegrityException::class)
|
|
||||||
fun verify(crypto: Crypto.CryptoManager) {
|
|
||||||
val hmac = this.hmac;
|
|
||||||
|
|
||||||
if (hmac == null) {
|
|
||||||
throw Exceptions.IntegrityException("HMAC is null!")
|
|
||||||
}
|
|
||||||
|
|
||||||
val correctHash = calculateHmac(crypto)
|
|
||||||
if (!Arrays.areEqual(hmac, correctHash)) {
|
|
||||||
throw Exceptions.IntegrityException("Bad HMAC. " + toHex(hmac) + " != " + toHex(correctHash))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun calculateHmac(crypto: Crypto.CryptoManager): ByteArray {
|
|
||||||
return super.calculateHmac(crypto, uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override fun toJson(): String {
|
|
||||||
val rawContent = content
|
|
||||||
content = Arrays.concatenate(hmac, rawContent)
|
|
||||||
val ret = super.toJson()
|
|
||||||
content = rawContent
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic
|
|
||||||
fun fakeWithUid(uid: String): Journal {
|
|
||||||
val ret = Journal()
|
|
||||||
ret.uid = uid
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun genUid(): String {
|
|
||||||
return sha256(UUID.randomUUID().toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Member {
|
|
||||||
val user: String?
|
|
||||||
val key: ByteArray?
|
|
||||||
val readOnly: Boolean
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
this.user = null
|
|
||||||
this.key = null
|
|
||||||
this.readOnly = false
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(user: String, encryptedKey: ByteArray, readOnly: Boolean = false) {
|
|
||||||
this.user = user
|
|
||||||
this.key = encryptedKey
|
|
||||||
this.readOnly = readOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun toJson(): String {
|
|
||||||
return GsonHelper.gson.toJson(this, javaClass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val journalType = object : TypeToken<List<Journal>>() {
|
|
||||||
|
|
||||||
}.type
|
|
||||||
private val memberType = object : TypeToken<List<Member>>() {
|
|
||||||
|
|
||||||
}.type
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,148 +0,0 @@
|
|||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.GsonHelper
|
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto.CryptoManager.Companion.HMAC_SIZE
|
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto.toHex
|
|
||||||
import okhttp3.*
|
|
||||||
import org.spongycastle.util.Arrays
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.HttpURLConnection
|
|
||||||
|
|
||||||
class UserInfoManager(httpClient: OkHttpClient, remote: HttpUrl) : BaseManager() {
|
|
||||||
init {
|
|
||||||
this.remote = remote.newBuilder()
|
|
||||||
.addPathSegments("api/v1/user")
|
|
||||||
.addPathSegment("")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
this.client = httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun fetch(owner: String): UserInfo? {
|
|
||||||
val remote = this.remote!!.newBuilder().addPathSegment(owner).addPathSegment("").build()
|
|
||||||
val request = Request.Builder()
|
|
||||||
.get()
|
|
||||||
.url(remote)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val response: Response
|
|
||||||
try {
|
|
||||||
response = newCall(request)
|
|
||||||
} catch (e: Exceptions.HttpException) {
|
|
||||||
return if (e.status == HttpURLConnection.HTTP_NOT_FOUND) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val body = response.body()
|
|
||||||
val ret = GsonHelper.gson.fromJson(body!!.charStream(), UserInfo::class.java)
|
|
||||||
ret.owner = owner
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun delete(userInfo: UserInfo) {
|
|
||||||
val remote = this.remote!!.newBuilder().addPathSegment(userInfo.owner!!).addPathSegment("").build()
|
|
||||||
val request = Request.Builder()
|
|
||||||
.delete()
|
|
||||||
.url(remote)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
newCall(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun create(userInfo: UserInfo) {
|
|
||||||
val body = RequestBody.create(BaseManager.JSON, userInfo.toJson())
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.post(body)
|
|
||||||
.url(remote!!)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
newCall(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.HttpException::class)
|
|
||||||
fun update(userInfo: UserInfo) {
|
|
||||||
val remote = this.remote!!.newBuilder().addPathSegment(userInfo.owner!!).addPathSegment("").build()
|
|
||||||
val body = RequestBody.create(BaseManager.JSON, userInfo.toJson())
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.put(body)
|
|
||||||
.url(remote)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
newCall(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserInfo {
|
|
||||||
@Transient
|
|
||||||
var owner: String? = null
|
|
||||||
@Transient
|
|
||||||
val plan: String? = null
|
|
||||||
val version: Byte?
|
|
||||||
val pubkey: ByteArray?
|
|
||||||
private var content: ByteArray? = null
|
|
||||||
|
|
||||||
fun getContent(crypto: Crypto.CryptoManager): ByteArray? {
|
|
||||||
val content = Arrays.copyOfRange(this.content!!, HMAC_SIZE, this.content!!.size)
|
|
||||||
return crypto.decrypt(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setContent(crypto: Crypto.CryptoManager, rawContent: ByteArray) {
|
|
||||||
val content = crypto.encrypt(rawContent)
|
|
||||||
this.content = Arrays.concatenate(calculateHmac(crypto, content), content)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(Exceptions.IntegrityException::class)
|
|
||||||
fun verify(crypto: Crypto.CryptoManager) {
|
|
||||||
if (this.content == null) {
|
|
||||||
// Nothing to verify.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val hmac = Arrays.copyOfRange(this.content!!, 0, HMAC_SIZE)
|
|
||||||
val content = Arrays.copyOfRange(this.content!!, HMAC_SIZE, this.content!!.size)
|
|
||||||
|
|
||||||
val correctHash = calculateHmac(crypto, content)
|
|
||||||
if (!Arrays.areEqual(hmac, correctHash)) {
|
|
||||||
throw Exceptions.IntegrityException("Bad HMAC. " + toHex(hmac) + " != " + toHex(correctHash))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateHmac(crypto: Crypto.CryptoManager, content: ByteArray?): ByteArray {
|
|
||||||
return crypto.hmac(Arrays.concatenate(content, pubkey))
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor() {
|
|
||||||
this.version = null
|
|
||||||
this.pubkey = null
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(crypto: Crypto.CryptoManager, owner: String, pubkey: ByteArray, content: ByteArray) {
|
|
||||||
this.owner = owner
|
|
||||||
this.pubkey = pubkey
|
|
||||||
version = crypto.version
|
|
||||||
setContent(crypto, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun toJson(): String {
|
|
||||||
return GsonHelper.gson.toJson(this, javaClass)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun generate(cryptoManager: Crypto.CryptoManager, owner: String): UserInfo {
|
|
||||||
val keyPair = Crypto.generateKeyPair()
|
|
||||||
return UserInfo(cryptoManager, owner, keyPair!!.publicKey, keyPair.privateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,248 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
|
||||||
*
|
|
||||||
* Licensed according to the LICENSE file in this repository.
|
|
||||||
*/
|
|
||||||
package com.etesync.syncadapter.journalmanager.util;
|
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Hex;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.ParseException;
|
|
||||||
|
|
||||||
public class ByteUtil {
|
|
||||||
|
|
||||||
public static byte[] combine(byte[]... elements) {
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
for (byte[] element : elements) {
|
|
||||||
baos.write(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
return baos.toByteArray();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[][] split(byte[] input, int firstLength, int secondLength) {
|
|
||||||
byte[][] parts = new byte[2][];
|
|
||||||
|
|
||||||
parts[0] = new byte[firstLength];
|
|
||||||
System.arraycopy(input, 0, parts[0], 0, firstLength);
|
|
||||||
|
|
||||||
parts[1] = new byte[secondLength];
|
|
||||||
System.arraycopy(input, firstLength, parts[1], 0, secondLength);
|
|
||||||
|
|
||||||
return parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[][] split(byte[] input, int firstLength, int secondLength, int thirdLength)
|
|
||||||
throws ParseException
|
|
||||||
{
|
|
||||||
if (input == null || firstLength < 0 || secondLength < 0 || thirdLength < 0 ||
|
|
||||||
input.length < firstLength + secondLength + thirdLength)
|
|
||||||
{
|
|
||||||
throw new ParseException("Input too small: " + (input == null ? null : Hex.encodeHex(input)), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[][] parts = new byte[3][];
|
|
||||||
|
|
||||||
parts[0] = new byte[firstLength];
|
|
||||||
System.arraycopy(input, 0, parts[0], 0, firstLength);
|
|
||||||
|
|
||||||
parts[1] = new byte[secondLength];
|
|
||||||
System.arraycopy(input, firstLength, parts[1], 0, secondLength);
|
|
||||||
|
|
||||||
parts[2] = new byte[thirdLength];
|
|
||||||
System.arraycopy(input, firstLength + secondLength, parts[2], 0, thirdLength);
|
|
||||||
|
|
||||||
return parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] trim(byte[] input, int length) {
|
|
||||||
byte[] result = new byte[length];
|
|
||||||
System.arraycopy(input, 0, result, 0, result.length);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] copyFrom(byte[] input) {
|
|
||||||
byte[] output = new byte[input.length];
|
|
||||||
System.arraycopy(input, 0, output, 0, output.length);
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte intsToByteHighAndLow(int highValue, int lowValue) {
|
|
||||||
return (byte)((highValue << 4 | lowValue) & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int highBitsToInt(byte value) {
|
|
||||||
return (value & 0xFF) >> 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int lowBitsToInt(byte value) {
|
|
||||||
return (value & 0xF);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int highBitsToMedium(int value) {
|
|
||||||
return (value >> 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int lowBitsToMedium(int value) {
|
|
||||||
return (value & 0xFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] shortToByteArray(int value) {
|
|
||||||
byte[] bytes = new byte[2];
|
|
||||||
shortToByteArray(bytes, 0, value);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int shortToByteArray(byte[] bytes, int offset, int value) {
|
|
||||||
bytes[offset+1] = (byte)value;
|
|
||||||
bytes[offset] = (byte)(value >> 8);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int shortToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
|
||||||
bytes[offset] = (byte)value;
|
|
||||||
bytes[offset+1] = (byte)(value >> 8);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] mediumToByteArray(int value) {
|
|
||||||
byte[] bytes = new byte[3];
|
|
||||||
mediumToByteArray(bytes, 0, value);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int mediumToByteArray(byte[] bytes, int offset, int value) {
|
|
||||||
bytes[offset + 2] = (byte)value;
|
|
||||||
bytes[offset + 1] = (byte)(value >> 8);
|
|
||||||
bytes[offset] = (byte)(value >> 16);
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] intToByteArray(int value) {
|
|
||||||
byte[] bytes = new byte[4];
|
|
||||||
intToByteArray(bytes, 0, value);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int intToByteArray(byte[] bytes, int offset, int value) {
|
|
||||||
bytes[offset + 3] = (byte)value;
|
|
||||||
bytes[offset + 2] = (byte)(value >> 8);
|
|
||||||
bytes[offset + 1] = (byte)(value >> 16);
|
|
||||||
bytes[offset] = (byte)(value >> 24);
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int intToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
|
||||||
bytes[offset] = (byte)value;
|
|
||||||
bytes[offset+1] = (byte)(value >> 8);
|
|
||||||
bytes[offset+2] = (byte)(value >> 16);
|
|
||||||
bytes[offset+3] = (byte)(value >> 24);
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] longToByteArray(long l) {
|
|
||||||
byte[] bytes = new byte[8];
|
|
||||||
longToByteArray(bytes, 0, l);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int longToByteArray(byte[] bytes, int offset, long value) {
|
|
||||||
bytes[offset + 7] = (byte)value;
|
|
||||||
bytes[offset + 6] = (byte)(value >> 8);
|
|
||||||
bytes[offset + 5] = (byte)(value >> 16);
|
|
||||||
bytes[offset + 4] = (byte)(value >> 24);
|
|
||||||
bytes[offset + 3] = (byte)(value >> 32);
|
|
||||||
bytes[offset + 2] = (byte)(value >> 40);
|
|
||||||
bytes[offset + 1] = (byte)(value >> 48);
|
|
||||||
bytes[offset] = (byte)(value >> 56);
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int longTo4ByteArray(byte[] bytes, int offset, long value) {
|
|
||||||
bytes[offset + 3] = (byte)value;
|
|
||||||
bytes[offset + 2] = (byte)(value >> 8);
|
|
||||||
bytes[offset + 1] = (byte)(value >> 16);
|
|
||||||
bytes[offset + 0] = (byte)(value >> 24);
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int byteArrayToShort(byte[] bytes) {
|
|
||||||
return byteArrayToShort(bytes, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int byteArrayToShort(byte[] bytes, int offset) {
|
|
||||||
return
|
|
||||||
(bytes[offset] & 0xff) << 8 | (bytes[offset + 1] & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The SSL patented 3-byte Value.
|
|
||||||
public static int byteArrayToMedium(byte[] bytes, int offset) {
|
|
||||||
return
|
|
||||||
(bytes[offset] & 0xff) << 16 |
|
|
||||||
(bytes[offset + 1] & 0xff) << 8 |
|
|
||||||
(bytes[offset + 2] & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int byteArrayToInt(byte[] bytes) {
|
|
||||||
return byteArrayToInt(bytes, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int byteArrayToInt(byte[] bytes, int offset) {
|
|
||||||
return
|
|
||||||
(bytes[offset] & 0xff) << 24 |
|
|
||||||
(bytes[offset + 1] & 0xff) << 16 |
|
|
||||||
(bytes[offset + 2] & 0xff) << 8 |
|
|
||||||
(bytes[offset + 3] & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int byteArrayToIntLittleEndian(byte[] bytes, int offset) {
|
|
||||||
return
|
|
||||||
(bytes[offset + 3] & 0xff) << 24 |
|
|
||||||
(bytes[offset + 2] & 0xff) << 16 |
|
|
||||||
(bytes[offset + 1] & 0xff) << 8 |
|
|
||||||
(bytes[offset] & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long byteArrayToLong(byte[] bytes) {
|
|
||||||
return byteArrayToLong(bytes, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long byteArray4ToLong(byte[] bytes, int offset) {
|
|
||||||
return
|
|
||||||
((bytes[offset + 0] & 0xffL) << 24) |
|
|
||||||
((bytes[offset + 1] & 0xffL) << 16) |
|
|
||||||
((bytes[offset + 2] & 0xffL) << 8) |
|
|
||||||
((bytes[offset + 3] & 0xffL));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long byteArray5ToLong(byte[] bytes, int offset) {
|
|
||||||
return
|
|
||||||
((bytes[offset] & 0xffL) << 32) |
|
|
||||||
((bytes[offset + 1] & 0xffL) << 24) |
|
|
||||||
((bytes[offset + 2] & 0xffL) << 16) |
|
|
||||||
((bytes[offset + 3] & 0xffL) << 8) |
|
|
||||||
((bytes[offset + 4] & 0xffL));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long byteArrayToLong(byte[] bytes, int offset) {
|
|
||||||
return
|
|
||||||
((bytes[offset] & 0xffL) << 56) |
|
|
||||||
((bytes[offset + 1] & 0xffL) << 48) |
|
|
||||||
((bytes[offset + 2] & 0xffL) << 40) |
|
|
||||||
((bytes[offset + 3] & 0xffL) << 32) |
|
|
||||||
((bytes[offset + 4] & 0xffL) << 24) |
|
|
||||||
((bytes[offset + 5] & 0xffL) << 16) |
|
|
||||||
((bytes[offset + 6] & 0xffL) << 8) |
|
|
||||||
((bytes[offset + 7] & 0xffL));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -9,13 +9,11 @@
|
|||||||
package com.etesync.syncadapter.model
|
package com.etesync.syncadapter.model
|
||||||
|
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import com.etesync.syncadapter.journalmanager.Constants
|
import com.etesync.journalmanager.Constants
|
||||||
import com.etesync.syncadapter.journalmanager.JournalManager
|
import com.etesync.journalmanager.JournalManager
|
||||||
import com.etesync.syncadapter.model.ServiceDB.Collections
|
import com.etesync.syncadapter.model.ServiceDB.Collections
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.annotations.Expose
|
import com.google.gson.annotations.Expose
|
||||||
import io.requery.Persistable
|
|
||||||
import io.requery.sql.EntityDataStore
|
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
class CollectionInfo : Serializable {
|
class CollectionInfo : Serializable {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package com.etesync.syncadapter.model
|
package com.etesync.syncadapter.model
|
||||||
|
|
||||||
import com.etesync.syncadapter.GsonHelper
|
import com.etesync.syncadapter.GsonHelper
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.journalmanager.JournalEntryManager
|
import com.etesync.journalmanager.JournalEntryManager
|
||||||
|
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ import at.bitfire.vcard4android.ContactsStorageException
|
|||||||
import com.etesync.syncadapter.AccountSettings
|
import com.etesync.syncadapter.AccountSettings
|
||||||
import com.etesync.syncadapter.Constants
|
import com.etesync.syncadapter.Constants
|
||||||
import com.etesync.syncadapter.R
|
import com.etesync.syncadapter.R
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions
|
import com.etesync.journalmanager.Exceptions
|
||||||
import com.etesync.syncadapter.journalmanager.JournalEntryManager
|
import com.etesync.journalmanager.JournalEntryManager
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import com.etesync.syncadapter.model.SyncEntry
|
import com.etesync.syncadapter.model.SyncEntry
|
||||||
|
@ -20,8 +20,8 @@ import com.etesync.syncadapter.AccountSettings
|
|||||||
import com.etesync.syncadapter.Constants
|
import com.etesync.syncadapter.Constants
|
||||||
import com.etesync.syncadapter.HttpClient
|
import com.etesync.syncadapter.HttpClient
|
||||||
import com.etesync.syncadapter.R
|
import com.etesync.syncadapter.R
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions
|
import com.etesync.journalmanager.Exceptions
|
||||||
import com.etesync.syncadapter.journalmanager.JournalEntryManager
|
import com.etesync.journalmanager.JournalEntryManager
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import com.etesync.syncadapter.model.SyncEntry
|
import com.etesync.syncadapter.model.SyncEntry
|
||||||
|
@ -23,9 +23,9 @@ import androidx.core.util.Pair
|
|||||||
import at.bitfire.ical4android.CalendarStorageException
|
import at.bitfire.ical4android.CalendarStorageException
|
||||||
import at.bitfire.vcard4android.ContactsStorageException
|
import at.bitfire.vcard4android.ContactsStorageException
|
||||||
import com.etesync.syncadapter.*
|
import com.etesync.syncadapter.*
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions
|
import com.etesync.journalmanager.Exceptions
|
||||||
import com.etesync.syncadapter.journalmanager.JournalManager
|
import com.etesync.journalmanager.JournalManager
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import com.etesync.syncadapter.model.JournalEntity
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
@ -51,7 +51,7 @@ class CachedJournalFetcher {
|
|||||||
for (journal in journalsManager.list()) {
|
for (journal in journalsManager.list()) {
|
||||||
val crypto: Crypto.CryptoManager
|
val crypto: Crypto.CryptoManager
|
||||||
if (journal.key != null) {
|
if (journal.key != null) {
|
||||||
crypto = Crypto.CryptoManager(journal.version, settings.keyPair!!, journal.key)
|
crypto = Crypto.CryptoManager(journal.version, settings.keyPair!!, journal.key!!)
|
||||||
} else {
|
} else {
|
||||||
crypto = Crypto.CryptoManager(journal.version, settings.password(), journal.uid!!)
|
crypto = Crypto.CryptoManager(journal.version, settings.password(), journal.uid!!)
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@ import at.bitfire.ical4android.InvalidCalendarException
|
|||||||
import at.bitfire.vcard4android.ContactsStorageException
|
import at.bitfire.vcard4android.ContactsStorageException
|
||||||
import com.etesync.syncadapter.*
|
import com.etesync.syncadapter.*
|
||||||
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
|
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions
|
import com.etesync.journalmanager.Exceptions
|
||||||
import com.etesync.syncadapter.journalmanager.JournalEntryManager
|
import com.etesync.journalmanager.JournalEntryManager
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.model.*
|
import com.etesync.syncadapter.model.*
|
||||||
import com.etesync.syncadapter.model.SyncEntry.Actions.ADD
|
import com.etesync.syncadapter.model.SyncEntry.Actions.ADD
|
||||||
|
@ -14,7 +14,7 @@ import at.bitfire.vcard4android.ContactsStorageException
|
|||||||
import com.etesync.syncadapter.AccountSettings
|
import com.etesync.syncadapter.AccountSettings
|
||||||
import com.etesync.syncadapter.Constants
|
import com.etesync.syncadapter.Constants
|
||||||
import com.etesync.syncadapter.R
|
import com.etesync.syncadapter.R
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions
|
import com.etesync.journalmanager.Exceptions
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.ui.AccountSettingsActivity
|
import com.etesync.syncadapter.ui.AccountSettingsActivity
|
||||||
import com.etesync.syncadapter.ui.DebugInfoActivity
|
import com.etesync.syncadapter.ui.DebugInfoActivity
|
||||||
|
@ -16,7 +16,7 @@ import at.bitfire.ical4android.Task
|
|||||||
import com.etesync.syncadapter.AccountSettings
|
import com.etesync.syncadapter.AccountSettings
|
||||||
import com.etesync.syncadapter.Constants
|
import com.etesync.syncadapter.Constants
|
||||||
import com.etesync.syncadapter.R
|
import com.etesync.syncadapter.R
|
||||||
import com.etesync.syncadapter.journalmanager.JournalEntryManager
|
import com.etesync.journalmanager.JournalEntryManager
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import com.etesync.syncadapter.model.SyncEntry
|
import com.etesync.syncadapter.model.SyncEntry
|
||||||
|
@ -28,9 +28,9 @@ import androidx.core.content.ContextCompat
|
|||||||
import at.bitfire.ical4android.TaskProvider
|
import at.bitfire.ical4android.TaskProvider
|
||||||
import at.bitfire.vcard4android.ContactsStorageException
|
import at.bitfire.vcard4android.ContactsStorageException
|
||||||
import com.etesync.syncadapter.*
|
import com.etesync.syncadapter.*
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions
|
import com.etesync.journalmanager.Exceptions
|
||||||
import com.etesync.syncadapter.journalmanager.JournalAuthenticator
|
import com.etesync.journalmanager.JournalAuthenticator
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import com.etesync.syncadapter.model.JournalEntity
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
|
@ -12,9 +12,9 @@ import android.widget.TextView
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.etesync.syncadapter.*
|
import com.etesync.syncadapter.*
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.journalmanager.JournalManager
|
import com.etesync.journalmanager.JournalManager
|
||||||
import com.etesync.syncadapter.journalmanager.UserInfoManager
|
import com.etesync.journalmanager.UserInfoManager
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import com.etesync.syncadapter.model.JournalEntity
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
@ -18,9 +18,9 @@ import androidx.appcompat.app.AlertDialog
|
|||||||
import com.etesync.syncadapter.AccountSettings
|
import com.etesync.syncadapter.AccountSettings
|
||||||
import com.etesync.syncadapter.HttpClient
|
import com.etesync.syncadapter.HttpClient
|
||||||
import com.etesync.syncadapter.R
|
import com.etesync.syncadapter.R
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.journalmanager.JournalManager
|
import com.etesync.journalmanager.JournalManager
|
||||||
import com.etesync.syncadapter.journalmanager.UserInfoManager
|
import com.etesync.journalmanager.UserInfoManager
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.syncadapter.requestSync
|
import com.etesync.syncadapter.syncadapter.requestSync
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
@ -84,7 +84,7 @@ open class ChangeEncryptionPasswordActivity : BaseActivity() {
|
|||||||
Logger.log.info("Finished deriving new key")
|
Logger.log.info("Finished deriving new key")
|
||||||
|
|
||||||
val userInfoContent = userInfo.getContent(cryptoManager)!!
|
val userInfoContent = userInfo.getContent(cryptoManager)!!
|
||||||
cryptoManager = Crypto.CryptoManager(userInfo.version.toInt(), new_key, "userInfo")
|
cryptoManager = Crypto.CryptoManager(userInfo.version!!.toInt(), new_key, "userInfo")
|
||||||
userInfo.setContent(cryptoManager, userInfoContent)
|
userInfo.setContent(cryptoManager, userInfoContent)
|
||||||
|
|
||||||
Logger.log.info("Fetching journal list")
|
Logger.log.info("Fetching journal list")
|
||||||
|
@ -12,7 +12,7 @@ import android.widget.TextView
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.ListFragment
|
import androidx.fragment.app.ListFragment
|
||||||
import com.etesync.syncadapter.*
|
import com.etesync.syncadapter.*
|
||||||
import com.etesync.syncadapter.journalmanager.JournalManager
|
import com.etesync.journalmanager.JournalManager
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import com.etesync.syncadapter.model.JournalEntity
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
import com.etesync.syncadapter.model.JournalModel
|
import com.etesync.syncadapter.model.JournalModel
|
||||||
|
@ -21,9 +21,9 @@ import androidx.loader.content.AsyncTaskLoader
|
|||||||
import androidx.loader.content.Loader
|
import androidx.loader.content.Loader
|
||||||
import at.bitfire.ical4android.TaskProvider
|
import at.bitfire.ical4android.TaskProvider
|
||||||
import com.etesync.syncadapter.*
|
import com.etesync.syncadapter.*
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions
|
import com.etesync.journalmanager.Exceptions
|
||||||
import com.etesync.syncadapter.journalmanager.JournalManager
|
import com.etesync.journalmanager.JournalManager
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import com.etesync.syncadapter.model.JournalEntity
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
import com.etesync.syncadapter.model.JournalModel
|
import com.etesync.syncadapter.model.JournalModel
|
||||||
|
@ -28,7 +28,7 @@ import androidx.core.content.ContextCompat
|
|||||||
import at.bitfire.vcard4android.ContactsStorageException
|
import at.bitfire.vcard4android.ContactsStorageException
|
||||||
import com.etesync.syncadapter.*
|
import com.etesync.syncadapter.*
|
||||||
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
|
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions.HttpException
|
import com.etesync.journalmanager.Exceptions.HttpException
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.model.EntryEntity
|
import com.etesync.syncadapter.model.EntryEntity
|
||||||
import com.etesync.syncadapter.model.JournalEntity
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
|
@ -20,9 +20,9 @@ import androidx.loader.app.LoaderManager
|
|||||||
import androidx.loader.content.AsyncTaskLoader
|
import androidx.loader.content.AsyncTaskLoader
|
||||||
import androidx.loader.content.Loader
|
import androidx.loader.content.Loader
|
||||||
import com.etesync.syncadapter.*
|
import com.etesync.syncadapter.*
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions
|
import com.etesync.journalmanager.Exceptions
|
||||||
import com.etesync.syncadapter.journalmanager.JournalManager
|
import com.etesync.journalmanager.JournalManager
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import com.etesync.syncadapter.model.JournalEntity
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
@ -10,13 +10,12 @@ package com.etesync.syncadapter.ui
|
|||||||
|
|
||||||
import android.accounts.Account
|
import android.accounts.Account
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.etesync.syncadapter.Constants
|
import com.etesync.syncadapter.Constants
|
||||||
import com.etesync.syncadapter.R
|
import com.etesync.syncadapter.R
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions.HttpException
|
import com.etesync.journalmanager.Exceptions.HttpException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class ExceptionInfoFragment : DialogFragment() {
|
class ExceptionInfoFragment : DialogFragment() {
|
||||||
|
@ -8,7 +8,7 @@ import android.os.Bundle
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.etesync.syncadapter.*
|
import com.etesync.syncadapter.*
|
||||||
import com.etesync.syncadapter.journalmanager.JournalManager
|
import com.etesync.journalmanager.JournalManager
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@ package com.etesync.syncadapter.ui.setup
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.etesync.syncadapter.HttpClient
|
import com.etesync.syncadapter.HttpClient
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions
|
import com.etesync.journalmanager.Exceptions
|
||||||
import com.etesync.syncadapter.journalmanager.JournalAuthenticator
|
import com.etesync.journalmanager.JournalAuthenticator
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
@ -22,9 +22,9 @@ import androidx.appcompat.app.AlertDialog
|
|||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import at.bitfire.ical4android.TaskProvider
|
import at.bitfire.ical4android.TaskProvider
|
||||||
import com.etesync.syncadapter.*
|
import com.etesync.syncadapter.*
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.journalmanager.Exceptions
|
import com.etesync.journalmanager.Exceptions
|
||||||
import com.etesync.syncadapter.journalmanager.UserInfoManager
|
import com.etesync.journalmanager.UserInfoManager
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
import com.etesync.syncadapter.model.CollectionInfo
|
||||||
import com.etesync.syncadapter.model.JournalEntity
|
import com.etesync.syncadapter.model.JournalEntity
|
||||||
|
@ -13,9 +13,9 @@ import com.etesync.syncadapter.Constants.KEY_ACCOUNT
|
|||||||
import com.etesync.syncadapter.HttpClient
|
import com.etesync.syncadapter.HttpClient
|
||||||
import com.etesync.syncadapter.InvalidAccountException
|
import com.etesync.syncadapter.InvalidAccountException
|
||||||
import com.etesync.syncadapter.R
|
import com.etesync.syncadapter.R
|
||||||
import com.etesync.syncadapter.journalmanager.Constants
|
import com.etesync.journalmanager.Constants
|
||||||
import com.etesync.syncadapter.journalmanager.Crypto
|
import com.etesync.journalmanager.Crypto
|
||||||
import com.etesync.syncadapter.journalmanager.UserInfoManager
|
import com.etesync.journalmanager.UserInfoManager
|
||||||
import com.etesync.syncadapter.log.Logger
|
import com.etesync.syncadapter.log.Logger
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.HttpClient
|
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Assert.assertNotEquals
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class AuthenticatorTest {
|
|
||||||
private var httpClient: OkHttpClient? = null
|
|
||||||
private var remote: HttpUrl? = null
|
|
||||||
|
|
||||||
@Before
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun setUp() {
|
|
||||||
httpClient = HttpClient.Builder().build().okHttpClient
|
|
||||||
remote = HttpUrl.parse("http://localhost:8000") // FIXME: hardcode for now, should make configureable
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun tearDown() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(IOException::class, Exceptions.HttpException::class)
|
|
||||||
fun testAuthToken() {
|
|
||||||
val journalAuthenticator = JournalAuthenticator(httpClient!!, remote!!)
|
|
||||||
val authToken = journalAuthenticator.getAuthToken(Helpers.USER, Helpers.PASSWORD)
|
|
||||||
assertNotEquals(authToken!!.length.toLong(), 0)
|
|
||||||
|
|
||||||
val httpClient2 = HttpClient.Builder(null, null, authToken).build().okHttpClient
|
|
||||||
val journalAuthenticator2 = JournalAuthenticator(httpClient2!!, remote!!)
|
|
||||||
journalAuthenticator2.invalidateAuthToken(authToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = Exceptions.UnauthorizedException::class)
|
|
||||||
@Throws(Exceptions.IntegrityException::class, Exceptions.VersionTooNewException::class, IOException::class, Exceptions.HttpException::class)
|
|
||||||
fun testNoUser() {
|
|
||||||
val journalAuthenticator = JournalAuthenticator(httpClient!!, remote!!)
|
|
||||||
val authToken = journalAuthenticator.getAuthToken(Helpers.USER, "BadPassword")
|
|
||||||
assertNotEquals(authToken!!.length.toLong(), 0)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.utils.Base64
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Assert.assertArrayEquals
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.spongycastle.util.encoders.Hex
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class EncryptionTest {
|
|
||||||
@Before
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun setUp() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun tearDown() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testDerivePassword() {
|
|
||||||
val key = Crypto.deriveKey(Helpers.USER, Helpers.PASSWORD)
|
|
||||||
assertEquals(key, Helpers.keyBase64)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(Exceptions.IntegrityException::class, Exceptions.GenericCryptoException::class)
|
|
||||||
fun testCryptoV1() {
|
|
||||||
val cryptoManager = Crypto.CryptoManager(1, Helpers.keyBase64, "TestSaltShouldBeJournalId")
|
|
||||||
|
|
||||||
val clearText = "This Is Some Test Cleartext."
|
|
||||||
val cipher = cryptoManager.encrypt(clearText.toByteArray())
|
|
||||||
assertEquals(clearText, String(cryptoManager.decrypt(cipher!!)!!))
|
|
||||||
|
|
||||||
val expected = "Lz+HUFzh1HdjxuGdQrBwBG1IzHT0ug6mO8fwePSbXtc="
|
|
||||||
assertEquals(expected, Base64.encodeToString(cryptoManager.hmac("Some test data".toByteArray()), Base64.NO_WRAP))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(Exceptions.IntegrityException::class, Exceptions.GenericCryptoException::class)
|
|
||||||
fun testCryptoV2() {
|
|
||||||
val cryptoManager = Crypto.CryptoManager(2, Helpers.keyBase64, "TestSaltShouldBeJournalId")
|
|
||||||
|
|
||||||
val clearText = "This Is Some Test Cleartext."
|
|
||||||
val cipher = cryptoManager.encrypt(clearText.toByteArray())
|
|
||||||
assertEquals(clearText, String(cryptoManager.decrypt(cipher!!)!!))
|
|
||||||
|
|
||||||
val expected = "XQ/A0gentOaE98R9wzf3zEIAHj4OH1GF8J4C6JiJupo="
|
|
||||||
assertEquals(expected, Base64.encodeToString(cryptoManager.hmac("Some test data".toByteArray()), Base64.NO_WRAP))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = Exceptions.VersionTooNewException::class)
|
|
||||||
@Throws(Exceptions.IntegrityException::class, Exceptions.VersionTooNewException::class)
|
|
||||||
fun testCryptoVersionTooNew() {
|
|
||||||
Crypto.CryptoManager(120, Helpers.keyBase64, "TestSaltShouldBeJournalId")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = Exceptions.IntegrityException::class)
|
|
||||||
@Throws(Exceptions.IntegrityException::class, Exceptions.VersionTooNewException::class)
|
|
||||||
fun testCryptoVersionOutOfRange() {
|
|
||||||
Crypto.CryptoManager(999, Helpers.keyBase64, "TestSaltShouldBeJournalId")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(Exceptions.IntegrityException::class, Exceptions.GenericCryptoException::class)
|
|
||||||
fun testAsymCrypto() {
|
|
||||||
val keyPair = Crypto.generateKeyPair()
|
|
||||||
val cryptoManager = Crypto.AsymmetricCryptoManager(keyPair!!)
|
|
||||||
|
|
||||||
val clearText = "This Is Some Test Cleartext.".toByteArray()
|
|
||||||
val cipher = cryptoManager.encrypt(keyPair.publicKey, clearText)
|
|
||||||
val clearText2 = cryptoManager.decrypt(cipher!!)
|
|
||||||
assertArrayEquals(clearText, clearText2)
|
|
||||||
|
|
||||||
// Mostly for coverage. Make sure it's the expected sha256 value.
|
|
||||||
assertEquals("ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb",
|
|
||||||
Hex.toHexString(Crypto.AsymmetricCryptoManager.getKeyFingerprint("a".toByteArray())).toLowerCase())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
internal object Helpers {
|
|
||||||
val USER = "test@localhost"
|
|
||||||
val USER2 = "test2@localhost"
|
|
||||||
val PASSWORD = "SomePassword"
|
|
||||||
val keyBase64 = "Gpn6j6WJ/9JJbVkWhmEfZjlqSps5rwEOzjUOO0rqufvb4vtT4UfRgx0uMivuGwjF7/8Y1z1glIASX7Oz/4l2jucgf+lAzg2oTZFodWkXRZCDmFa7c9a8/04xIs7koFmUH34Rl9XXW6V2/GDVigQhQU8uWnrGo795tupoNQMbtB8RgMX5GyuxR55FvcybHpYBbwrDIsKvXcBxWFEscdNU8zyeq3yjvDo/W/y24dApW3mnNo7vswoL2rpkZj3dqw=="
|
|
||||||
}
|
|
@ -1,265 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the GNU Public License v3.0
|
|
||||||
* which accompanies this distribution, and is available at
|
|
||||||
* http://www.gnu.org/licenses/gpl.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.etesync.syncadapter.journalmanager
|
|
||||||
|
|
||||||
import com.etesync.syncadapter.App
|
|
||||||
import com.etesync.syncadapter.HttpClient
|
|
||||||
import com.etesync.syncadapter.model.CollectionInfo
|
|
||||||
import okhttp3.*
|
|
||||||
import okio.BufferedSink
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Assert.*
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class ServiceTest {
|
|
||||||
private var httpClient: OkHttpClient? = null
|
|
||||||
private var remote: HttpUrl? = null
|
|
||||||
private var authToken: String? = null
|
|
||||||
|
|
||||||
@Before
|
|
||||||
@Throws(Exception::class)
|
|
||||||
fun setUp() {
|
|
||||||
httpClient = HttpClient.Builder().build().okHttpClient
|
|
||||||
remote = HttpUrl.parse("http://localhost:8000") // FIXME: hardcode for now, should make configureable
|
|
||||||
val journalAuthenticator = JournalAuthenticator(httpClient!!, remote!!)
|
|
||||||
authToken = journalAuthenticator.getAuthToken(Helpers.USER, Helpers.PASSWORD)
|
|
||||||
|
|
||||||
httpClient = HttpClient.Builder(null, null, authToken!!).build().okHttpClient
|
|
||||||
|
|
||||||
/* Reset */
|
|
||||||
val request = Request.Builder()
|
|
||||||
.post(object : RequestBody() {
|
|
||||||
override fun contentType(): MediaType? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun writeTo(sink: BufferedSink) {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.url(remote!!.newBuilder().addEncodedPathSegments("reset/").build())
|
|
||||||
.build()
|
|
||||||
val response = httpClient!!.newCall(request).execute()
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
throw Exception("Failed resetting")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun tearDown() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(IOException::class, Exceptions.HttpException::class, Exceptions.GenericCryptoException::class, Exceptions.IntegrityException::class)
|
|
||||||
fun testSyncSimple() {
|
|
||||||
var caught: Exception?
|
|
||||||
val journalManager = JournalManager(httpClient!!, remote!!)
|
|
||||||
val info = CollectionInfo.defaultForServiceType(CollectionInfo.Type.ADDRESS_BOOK)
|
|
||||||
info.uid = JournalManager.Journal.genUid()
|
|
||||||
info.displayName = "Test"
|
|
||||||
val crypto = Crypto.CryptoManager(info.version, Helpers.keyBase64, info.uid!!)
|
|
||||||
var journal = JournalManager.Journal(crypto, info.toJson(), info.uid!!)
|
|
||||||
journalManager.create(journal)
|
|
||||||
|
|
||||||
// Try pushing the same journal (uid clash)
|
|
||||||
try {
|
|
||||||
caught = null
|
|
||||||
journalManager.create(journal)
|
|
||||||
} catch (e: Exceptions.HttpException) {
|
|
||||||
caught = e
|
|
||||||
}
|
|
||||||
|
|
||||||
assertNotNull(caught)
|
|
||||||
|
|
||||||
var journals: List<JournalManager.Journal> = journalManager.list()
|
|
||||||
assertEquals(journals.size.toLong(), 1)
|
|
||||||
var info2 = CollectionInfo.fromJson(journals[0].getContent(crypto))
|
|
||||||
assertEquals(info2.displayName, info.displayName)
|
|
||||||
|
|
||||||
// Update journal
|
|
||||||
info.displayName = "Test 2"
|
|
||||||
journal = JournalManager.Journal(crypto, info.toJson(), info.uid!!)
|
|
||||||
journalManager.update(journal)
|
|
||||||
|
|
||||||
journals = journalManager.list()
|
|
||||||
assertEquals(journals.size.toLong(), 1)
|
|
||||||
info2 = CollectionInfo.fromJson(journals[0].getContent(crypto))
|
|
||||||
assertEquals(info2.displayName, info.displayName)
|
|
||||||
|
|
||||||
// Delete journal
|
|
||||||
journalManager.delete(journal)
|
|
||||||
|
|
||||||
journals = journalManager.list()
|
|
||||||
assertEquals(journals.size.toLong(), 0)
|
|
||||||
|
|
||||||
// Bad HMAC
|
|
||||||
info.uid = JournalManager.Journal.genUid()
|
|
||||||
journal = JournalManager.Journal(crypto, info.toJson(), info.uid!!)
|
|
||||||
info.displayName = "Test 3"
|
|
||||||
//// We assume this doesn't update the hmac.
|
|
||||||
journal.setContent(crypto, info.toJson())
|
|
||||||
journalManager.create(journal)
|
|
||||||
|
|
||||||
try {
|
|
||||||
caught = null
|
|
||||||
for (journal1 in journalManager.list()) {
|
|
||||||
val crypto1 = Crypto.CryptoManager(info.version, Helpers.keyBase64, journal1.uid!!)
|
|
||||||
journal1.verify(crypto1)
|
|
||||||
}
|
|
||||||
} catch (e: Exceptions.IntegrityException) {
|
|
||||||
caught = e
|
|
||||||
}
|
|
||||||
|
|
||||||
assertNotNull(caught)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(IOException::class, Exceptions.HttpException::class, Exceptions.GenericCryptoException::class, Exceptions.IntegrityException::class)
|
|
||||||
fun testSyncEntry() {
|
|
||||||
var caught: Exception?
|
|
||||||
val journalManager = JournalManager(httpClient!!, remote!!)
|
|
||||||
val info = CollectionInfo.defaultForServiceType(CollectionInfo.Type.ADDRESS_BOOK)
|
|
||||||
info.uid = JournalManager.Journal.genUid()
|
|
||||||
info.displayName = "Test"
|
|
||||||
val crypto = Crypto.CryptoManager(info.version, Helpers.keyBase64, info.uid!!)
|
|
||||||
val journal = JournalManager.Journal(crypto, info.toJson(), info.uid!!)
|
|
||||||
journalManager.create(journal)
|
|
||||||
|
|
||||||
val journalEntryManager = JournalEntryManager(httpClient!!, remote!!, info.uid!!)
|
|
||||||
var previousEntry: JournalEntryManager.Entry? = null
|
|
||||||
val entry = JournalEntryManager.Entry()
|
|
||||||
entry.update(crypto, "Content", previousEntry)
|
|
||||||
|
|
||||||
var entries: MutableList<JournalEntryManager.Entry> = LinkedList()
|
|
||||||
var retEntries: List<JournalEntryManager.Entry>
|
|
||||||
|
|
||||||
entries.add(entry)
|
|
||||||
journalEntryManager.create(entries, null)
|
|
||||||
previousEntry = entry
|
|
||||||
|
|
||||||
entries.clear()
|
|
||||||
var entry2 = JournalEntryManager.Entry()
|
|
||||||
entry2.update(crypto, "Content", previousEntry)
|
|
||||||
entries.add(entry2)
|
|
||||||
|
|
||||||
// Pushing a correct entries without the last parameter
|
|
||||||
try {
|
|
||||||
caught = null
|
|
||||||
journalEntryManager.create(entries, null)
|
|
||||||
} catch (e: Exceptions.HttpException) {
|
|
||||||
caught = e
|
|
||||||
}
|
|
||||||
|
|
||||||
assertNotNull(caught)
|
|
||||||
|
|
||||||
// Adding a second entry
|
|
||||||
journalEntryManager.create(entries, previousEntry.uid)
|
|
||||||
previousEntry = entry2
|
|
||||||
|
|
||||||
entries.clear()
|
|
||||||
entries.add(entry)
|
|
||||||
entries.add(entry2)
|
|
||||||
|
|
||||||
// Check last works:
|
|
||||||
retEntries = journalEntryManager.list(crypto, entry.uid, 0)
|
|
||||||
assertEquals(retEntries.size.toLong(), 1)
|
|
||||||
retEntries = journalEntryManager.list(crypto, entry2.uid, 0)
|
|
||||||
assertEquals(retEntries.size.toLong(), 0)
|
|
||||||
|
|
||||||
// Corrupt the journal and verify we catch it
|
|
||||||
entries.clear()
|
|
||||||
entry2 = JournalEntryManager.Entry()
|
|
||||||
entry2.update(crypto, "Content", null)
|
|
||||||
entries.add(entry2)
|
|
||||||
|
|
||||||
journalEntryManager.create(entries, previousEntry.uid)
|
|
||||||
|
|
||||||
try {
|
|
||||||
caught = null
|
|
||||||
journalEntryManager.list(crypto, null, 0)
|
|
||||||
} catch (e: Exceptions.IntegrityException) {
|
|
||||||
caught = e
|
|
||||||
}
|
|
||||||
|
|
||||||
assertNotNull(caught)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(IOException::class, Exceptions.HttpException::class, Exceptions.GenericCryptoException::class, Exceptions.IntegrityException::class)
|
|
||||||
fun testUserInfo() {
|
|
||||||
val cryptoManager = Crypto.CryptoManager(Constants.CURRENT_VERSION, Helpers.keyBase64, "userInfo")
|
|
||||||
var userInfo: UserInfoManager.UserInfo?
|
|
||||||
var userInfo2: UserInfoManager.UserInfo?
|
|
||||||
val manager = UserInfoManager(httpClient!!, remote!!)
|
|
||||||
|
|
||||||
// Get when there's nothing
|
|
||||||
userInfo = manager.fetch(Helpers.USER)
|
|
||||||
assertNull(userInfo)
|
|
||||||
|
|
||||||
// Create
|
|
||||||
userInfo = UserInfoManager.UserInfo.generate(cryptoManager, Helpers.USER)
|
|
||||||
manager.create(userInfo)
|
|
||||||
|
|
||||||
// Get
|
|
||||||
userInfo2 = manager.fetch(Helpers.USER)
|
|
||||||
assertNotNull(userInfo2)
|
|
||||||
assertArrayEquals(userInfo.getContent(cryptoManager), userInfo2!!.getContent(cryptoManager))
|
|
||||||
|
|
||||||
// Update
|
|
||||||
userInfo.setContent(cryptoManager, "test".toByteArray())
|
|
||||||
manager.update(userInfo)
|
|
||||||
userInfo2 = manager.fetch(Helpers.USER)
|
|
||||||
assertNotNull(userInfo2)
|
|
||||||
assertArrayEquals(userInfo.getContent(cryptoManager), userInfo2!!.getContent(cryptoManager))
|
|
||||||
|
|
||||||
// Delete
|
|
||||||
manager.delete(userInfo)
|
|
||||||
userInfo = manager.fetch(Helpers.USER)
|
|
||||||
assertNull(userInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(IOException::class, Exceptions.HttpException::class, Exceptions.GenericCryptoException::class, Exceptions.IntegrityException::class)
|
|
||||||
fun testJournalMember() {
|
|
||||||
val journalManager = JournalManager(httpClient!!, remote!!)
|
|
||||||
val info = CollectionInfo.defaultForServiceType(CollectionInfo.Type.ADDRESS_BOOK)
|
|
||||||
info.uid = JournalManager.Journal.genUid()
|
|
||||||
info.displayName = "Test"
|
|
||||||
val crypto = Crypto.CryptoManager(info.version, Helpers.keyBase64, info.uid!!)
|
|
||||||
val journal = JournalManager.Journal(crypto, info.toJson(), info.uid!!)
|
|
||||||
journalManager.create(journal)
|
|
||||||
|
|
||||||
assertEquals(journalManager.listMembers(journal).size.toLong(), 0)
|
|
||||||
|
|
||||||
// Test inviting ourselves
|
|
||||||
val member = JournalManager.Member(Helpers.USER, "test".toByteArray())
|
|
||||||
journalManager.addMember(journal, member)
|
|
||||||
// We shouldn't show in the list
|
|
||||||
assertEquals(journalManager.listMembers(journal).size.toLong(), 0)
|
|
||||||
// Though we should have a key in the journal
|
|
||||||
assertNotNull(journalManager.list().first().key)
|
|
||||||
|
|
||||||
val member2 = JournalManager.Member(Helpers.USER2, "test".toByteArray())
|
|
||||||
journalManager.addMember(journal, member2)
|
|
||||||
assertEquals(journalManager.listMembers(journal).size.toLong(), 1)
|
|
||||||
|
|
||||||
// Uninviting user
|
|
||||||
journalManager.deleteMember(journal, member2)
|
|
||||||
|
|
||||||
assertEquals(journalManager.listMembers(journal).size.toLong(), 0)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user