Skip to content

Commit

Permalink
Merge community security (#166)
Browse files Browse the repository at this point in the history
* merge community
  • Loading branch information
aman-alfresco authored Sep 12, 2023
1 parent e4131eb commit d90359b
Show file tree
Hide file tree
Showing 79 changed files with 987 additions and 126 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
schedule:
- cron: '0 15 * * 1-5'
workflow_dispatch:
push:
pull_request:

env:
BROWSERSTACK_ACCESSKEY: ${{ secrets.BROWSERSTACK_ACCESSKEY }}
Expand All @@ -16,6 +18,18 @@ env:


jobs:
pmd_scan:
name: "PMD Scan"
runs-on: ubuntu-latest
if: >
github.event_name == 'pull_request' &&
!contains(github.event.head_commit.message, '[skip pmd]') &&
!contains(github.event.head_commit.message, '[force]')
steps:
- uses: Alfresco/[email protected]
with:
fail-on-new-issues: "false"
create-github-annotations: "false"
build:
runs-on: ubuntu-latest

Expand Down
2 changes: 2 additions & 0 deletions account/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ android {

dependencies {
implementation libs.kotlin.stdlib

implementation libs.androidx.security.crypto
}
42 changes: 31 additions & 11 deletions account/src/main/kotlin/com/alfresco/content/account/Account.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package com.alfresco.content.account
import android.accounts.AccountManager
import android.content.Context
import android.os.Bundle
import com.alfresco.content.account.SecureSharedPreferencesManager.Companion.KEY_DISPLAY_NAME
import com.alfresco.content.account.SecureSharedPreferencesManager.Companion.KEY_EMAIL
import com.alfresco.content.account.SecureSharedPreferencesManager.Companion.KEY_PASSWORD
import android.accounts.Account as AndroidAccount

data class Account(
Expand Down Expand Up @@ -34,22 +37,31 @@ data class Account(
email: String,
myFiles: String,
) {
val sharedSecure = SecureSharedPreferencesManager(context)

val b = Bundle()
b.putString(authTypeKey, authType)
b.putString(authConfigKey, authConfig)
b.putString(serverKey, serverUrl)
b.putString(displayNameKey, displayName)
b.putString(emailKey, email)
b.putString(displayNameKey, KEY_DISPLAY_NAME)
b.putString(emailKey, KEY_EMAIL)
b.putString(myFilesKey, myFiles)
val acc = AndroidAccount(id, context.getString(R.string.android_auth_account_type))
AccountManager.get(context).addAccountExplicitly(acc, authState, b)

// Save credentials securely using the SecureSharedPreferencesManager
sharedSecure.saveCredentials(email, authState, displayName)

AccountManager.get(context).addAccountExplicitly(acc, KEY_PASSWORD, b)
}

fun update(context: Context, id: String, authState: String) {
val am = AccountManager.get(context)
val acc = getAndroidAccount(context)
val sharedSecure = SecureSharedPreferencesManager(context)
if (acc?.name == id) {
am.setPassword(acc, authState)
// Save credentials securely using the SecureSharedPreferencesManager
sharedSecure.savePassword(authState)
am.setPassword(acc, KEY_PASSWORD)
}
}

Expand All @@ -61,11 +73,16 @@ data class Account(
email: String,
myFiles: String,
) {
val sharedSecure = SecureSharedPreferencesManager(context)
val am = AccountManager.get(context)
val acc = getAndroidAccount(context)
am.setPassword(acc, authState)
am.setUserData(acc, displayNameKey, displayName)
am.setUserData(acc, emailKey, email)

// Save credentials securely using the SecureSharedPreferencesManager
sharedSecure.saveCredentials(email, authState, displayName)

am.setPassword(acc, KEY_PASSWORD)
am.setUserData(acc, displayNameKey, KEY_DISPLAY_NAME)
am.setUserData(acc, emailKey, KEY_EMAIL)
am.setUserData(acc, myFilesKey, myFiles)

if (acc?.name != id) {
Expand All @@ -83,16 +100,19 @@ data class Account(
fun getAccount(context: Context): Account? {
val am = AccountManager.get(context)
val accountList = am.getAccountsByType(context.getString(R.string.android_auth_account_type))
if (accountList.isNotEmpty()) {
val sharedSecure = SecureSharedPreferencesManager(context)
// get credentials securely using the SecureSharedPreferencesManager
val secureCredentials = sharedSecure.getSavedCredentials()
if (accountList.isNotEmpty() && secureCredentials != null) {
val acc = accountList[0]
return Account(
acc.name,
am.getPassword(acc),
secureCredentials.second,
am.getUserData(acc, authTypeKey),
am.getUserData(acc, authConfigKey),
am.getUserData(acc, serverKey),
am.getUserData(acc, displayNameKey),
am.getUserData(acc, emailKey),
secureCredentials.third,
secureCredentials.first,
am.getUserData(acc, myFilesKey),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.alfresco.content.account

import android.content.Context
import android.util.Log
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey

/**
* Marked as SecureSharedPreferencesManager
*/
class SecureSharedPreferencesManager(private val context: Context) {

private val masterKey: MasterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()

fun saveCredentials(email: String, password: String, displayName: String) {
try {
val encryptedSharedPreferences = EncryptedSharedPreferences.create(
context,
KEY_PREF_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)

encryptedSharedPreferences.edit()
.putString(KEY_EMAIL, email)
.putString(KEY_DISPLAY_NAME, displayName)
.putString(KEY_PASSWORD, password)
.apply()

Log.d(TAG, "Credentials saved securely")
} catch (e: Exception) {
Log.e(TAG, "Error saving credentials: ${e.message}")
}
}

fun savePassword(password: String) {
try {
val encryptedSharedPreferences = EncryptedSharedPreferences.create(
context,
KEY_PREF_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)

encryptedSharedPreferences.edit()
.putString(KEY_PASSWORD, password)
.apply()

Log.d(TAG, "Password saved securely")
} catch (e: Exception) {
Log.e(TAG, "Error saving credentials: ${e.message}")
}
}

fun getSavedCredentials(): Triple<String, String, String>? {
try {
val encryptedSharedPreferences = EncryptedSharedPreferences.create(
context,
KEY_PREF_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)

val email = encryptedSharedPreferences.getString(KEY_EMAIL, null)
val password = encryptedSharedPreferences.getString(KEY_PASSWORD, null)
val displayName = encryptedSharedPreferences.getString(KEY_DISPLAY_NAME, null)

if (email != null && password != null && displayName != null) {
return Triple(email, password, displayName)
}
} catch (e: Exception) {
Log.e(TAG, "Error retrieving credentials: ${e.message}")
}

return null
}

companion object {
const val KEY_DISPLAY_NAME = "display_name"
const val KEY_EMAIL = "email"
const val KEY_PASSWORD = "password"
const val KEY_PREF_NAME = "secure_prefs"

val TAG: String = SecureSharedPreferencesManager::class.java.simpleName
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.MavericksView
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.alfresco.content.actions.sheet.ProcessDefinitionsSheet
import com.alfresco.content.common.EntryListener
import com.alfresco.content.data.ContextualActionData
import com.alfresco.content.data.Entry
import com.alfresco.content.data.ParentEntry
import com.alfresco.events.on
import com.alfresco.ui.getDrawableForAttribute
import kotlinx.coroutines.delay

class ContextualActionsBarFragment : Fragment(), MavericksView {
class ContextualActionsBarFragment : Fragment(), MavericksView, EntryListener {
private val viewModel: ContextualActionsViewModel by fragmentViewModel()
private lateinit var view: LinearLayout

Expand All @@ -40,6 +44,7 @@ class ContextualActionsBarFragment : Fragment(), MavericksView {
delay(1000)
requireActivity().onBackPressed()
}
viewModel.setEntryListener(this)
}

override fun invalidate() = withState(viewModel) {
Expand Down Expand Up @@ -117,4 +122,12 @@ class ContextualActionsBarFragment : Fragment(), MavericksView {
}
}
}

override fun onProcessStart(entries: List<ParentEntry>) {
super.onProcessStart(entries)
if (isAdded && isVisible && isResumed) {
ProcessDefinitionsSheet.with(entries.map { it as Entry }).showNow(requireActivity().supportFragmentManager, null)
requireActivity().supportFragmentManager.executePendingTransactions()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import com.alfresco.content.common.EntryListener
import com.alfresco.content.data.BrowseRepository
import com.alfresco.content.data.Entry
import com.alfresco.content.data.FavoritesRepository
Expand All @@ -22,6 +23,8 @@ class ContextualActionsViewModel(
private val settings: Settings,
) : MavericksViewModel<ContextualActionsState>(state) {

var listener: EntryListener? = null

init {
if (!state.isMultiSelection) {
buildModelSingleSelection()
Expand Down Expand Up @@ -71,6 +74,10 @@ class ContextualActionsViewModel(
}

private fun updateState(action: Action) {
val entry = action.entry as Entry
if (action is ActionStartProcess) {
onStartProcess(action.entries.ifEmpty { listOf(entry) })
}
setState {
val entry = action.entry as Entry

Expand All @@ -82,6 +89,16 @@ class ContextualActionsViewModel(
}
}

fun setEntryListener(listener: EntryListener) {
this.listener = listener
}

private fun onStartProcess(entries: List<Entry>) = entries.run {
if (entries.all { it.isFile }) {
listener?.onProcessStart(entries)
}
}

private fun fetchEntry(entry: Entry): Flow<Entry> =
when (entry.type) {
Entry.Type.SITE -> FavoritesRepository()::getFavoriteSite.asFlow(entry.id)
Expand Down Expand Up @@ -115,16 +132,18 @@ class ContextualActionsViewModel(

when {
state.entries.all { it.isTrashed } -> {
// Added restore and delete actions
actions.add(ActionRestore(entry, state.entries))
actions.add(ActionDeleteForever(entry, state.entries))
}

state.entries.all { it.hasOfflineStatus } -> {
// Added Favorite Action
actions.addAll(sharedActions(entry, state.entries))
// Added Offline action
actions.add(offlineMultiActionFor(entry, state.entries))
}

else -> {
// Added common actions
actions.addAll(sharedActions(entry, state.entries))
}
}
Expand Down Expand Up @@ -252,6 +271,6 @@ class ContextualActionsViewModel(
state: ContextualActionsState,
) =
// Requires activity context in order to present other fragments
ContextualActionsViewModel(state, viewModelContext.activity(), Settings(viewModelContext.activity))
ContextualActionsViewModel(state, viewModelContext.activity, Settings(viewModelContext.activity))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@ internal class ActionCreateViewModel(
}
if (distributionVersion() == Settings.DistributionVersion.ENTERPRISE) {
if (parent.uploadServer == UploadServerType.DEFAULT) {
actions.add(ActionCaptureMedia(parent))
actions.add(ActionScanDocument(parent))
}
actions.add(ActionCaptureMedia(parent, title = R.string.action_capture_media_title_multiple))
} else {
actions.add(ActionCaptureMedia(parent, title = R.string.action_capture_media_title_single))
actions.add(ActionUploadFiles(parent, title = R.string.action_capture_media_title_single))
}
actions.add(ActionUploadMedia(parent))
actions.add(ActionUploadFiles(parent))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.alfresco.content.actions.sheet

import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.airbnb.epoxy.ModelProp
import com.airbnb.epoxy.ModelView
import com.alfresco.content.actions.databinding.ViewListProcessMessageBinding

@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT)
class ListViewProcessErrorMessage @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr) {

private val binding = ViewListProcessMessageBinding.inflate(LayoutInflater.from(context), this)

@ModelProp
fun setIconRes(@DrawableRes drawableRes: Int) {
binding.icon.setImageResource(drawableRes)
}

@ModelProp
fun setTitle(@StringRes stringRes: Int) {
binding.title.text = resources.getText(stringRes)
}

@ModelProp
fun setMessage(@StringRes stringRes: Int) {
binding.message.text = resources.getText(stringRes)
}
}
Loading

0 comments on commit d90359b

Please sign in to comment.