Start working on audit logs

This commit is contained in:
Michael Schättgen 2024-03-31 22:32:56 +02:00
parent 3c124deae1
commit 171da34b13
31 changed files with 834 additions and 7 deletions

View File

@ -139,10 +139,13 @@ dependencies {
def hiltVersion = '2.50'
def junitVersion = '4.13.2'
def libsuVersion = '5.2.2'
def roomVersion = "2.6.1"
annotationProcessor 'androidx.annotation:annotation:1.7.1'
annotationProcessor "com.google.dagger:hilt-compiler:$hiltVersion"
annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}"
annotationProcessor "androidx.room:room-compiler:$roomVersion"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.activity:activity:1.8.2'
@ -157,6 +160,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-process:2.6.2"
implementation "androidx.preference:preference:1.2.1"
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation "androidx.room:room-runtime:$roomVersion"
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.caverock:androidsvg-aar:1.4'

View File

@ -17,14 +17,23 @@ import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.room.Room;
import com.beemdevelopment.aegis.database.AppDatabase;
import com.beemdevelopment.aegis.database.AuditLogEntry;
import com.beemdevelopment.aegis.database.AuditLogRepository;
import com.beemdevelopment.aegis.receivers.VaultLockReceiver;
import com.beemdevelopment.aegis.ui.MainActivity;
import com.beemdevelopment.aegis.util.IOUtils;
import com.beemdevelopment.aegis.vault.VaultManager;
import com.topjohnwu.superuser.Shell;
import org.checkerframework.checker.units.qual.A;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import dagger.hilt.InstallIn;
import dagger.hilt.android.EarlyEntryPoint;

View File

@ -8,6 +8,8 @@ import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import com.beemdevelopment.aegis.database.AppDatabase;
import com.beemdevelopment.aegis.database.AuditLogRepository;
import com.beemdevelopment.aegis.util.IOUtils;
import com.beemdevelopment.aegis.vault.VaultFile;
import com.beemdevelopment.aegis.vault.VaultRepository;
@ -25,12 +27,16 @@ public class AegisBackupAgent extends BackupAgent {
private Preferences _prefs;
private AuditLogRepository _auditLogRepository;
@Override
public void onCreate() {
super.onCreate();
// Cannot use injection with Dagger Hilt here, because the app is launched in a restricted mode on restore
_prefs = new Preferences(this);
AppDatabase appDatabase = AegisModule.provideAppDatabase(this);
_auditLogRepository = AegisModule.provideAuditLogRepository(appDatabase);
}
@Override
@ -53,6 +59,7 @@ public class AegisBackupAgent extends BackupAgent {
// report any runtime exceptions, in addition to the expected IOExceptions.
try {
fullBackup(data);
_auditLogRepository.addAndroidBackupCreatedEvent();
_prefs.setAndroidBackupResult(new Preferences.BackupResult(null));
} catch (Exception e) {
Log.e(TAG, String.format("onFullBackup() failed: %s", e));

View File

@ -2,6 +2,11 @@ package com.beemdevelopment.aegis;
import android.content.Context;
import androidx.room.Room;
import com.beemdevelopment.aegis.database.AppDatabase;
import com.beemdevelopment.aegis.database.AuditLogDao;
import com.beemdevelopment.aegis.database.AuditLogRepository;
import com.beemdevelopment.aegis.icons.IconPackManager;
import com.beemdevelopment.aegis.vault.VaultManager;
@ -24,12 +29,27 @@ public class AegisModule {
@Provides
@Singleton
public static VaultManager provideVaultManager(@ApplicationContext Context context) {
return new VaultManager(context);
public static AuditLogRepository provideAuditLogRepository(AppDatabase appDatabase) {
AuditLogDao auditLogDao = appDatabase.auditLogDao();
return new AuditLogRepository(auditLogDao);
}
@Provides
@Singleton
public static VaultManager provideVaultManager(@ApplicationContext Context context, AuditLogRepository auditLogRepository) {
return new VaultManager(context, auditLogRepository);
}
@Provides
public static Preferences providePreferences(@ApplicationContext Context context) {
return new Preferences(context);
}
@Provides
@Singleton
public static AppDatabase provideAppDatabase(@ApplicationContext Context context) {
return Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "aegis-db")
.build();
}
}

View File

@ -0,0 +1,42 @@
package com.beemdevelopment.aegis;
public enum EventType {
VAULT_UNLOCKED,
VAULT_BACKUP_CREATED,
VAULT_ANDROID_BACKUP_CREATED,
VAULT_EXPORTED,
ENTRY_SHARED,
VAULT_UNLOCK_FAILED_PASSWORD,
VAULT_UNLOCK_FAILED_BIOMETRICS;
private static EventType[] _values;
static {
_values = values();
}
public static EventType fromInteger(int x) {
return _values[x];
}
public static int getEventTitleRes(EventType eventType) {
switch (eventType) {
case VAULT_UNLOCKED:
return R.string.event_title_vault_unlocked;
case VAULT_BACKUP_CREATED:
return R.string.event_title_backup_created;
case VAULT_ANDROID_BACKUP_CREATED:
return R.string.event_title_android_backup_created;
case VAULT_EXPORTED:
return R.string.event_title_vault_exported;
case ENTRY_SHARED:
return R.string.event_title_entry_shared;
case VAULT_UNLOCK_FAILED_PASSWORD:
return R.string.event_title_vault_unlock_failed_password;
case VAULT_UNLOCK_FAILED_BIOMETRICS:
return R.string.event_title_vault_unlock_failed_biometrics;
default:
return R.string.event_unknown;
}
}
}

View File

@ -0,0 +1,15 @@
package com.beemdevelopment.aegis.database;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Database(entities = {AuditLogEntry.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract AuditLogDao auditLogDao();
}

View File

@ -0,0 +1,17 @@
package com.beemdevelopment.aegis.database;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface AuditLogDao {
@Insert
void insert(AuditLogEntry log);
@Query("SELECT * FROM audit_logs WHERE timestamp >= strftime('%s', 'now', '-30 days') ORDER BY timestamp DESC")
LiveData<List<AuditLogEntry>> getAll();
}

View File

@ -0,0 +1,61 @@
package com.beemdevelopment.aegis.database;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import com.beemdevelopment.aegis.EventType;
@Entity(tableName = "audit_logs")
public class AuditLogEntry {
@PrimaryKey(autoGenerate = true)
protected long id;
@NonNull
@ColumnInfo(name = "event_type")
private final EventType _eventType;
@ColumnInfo(name = "reference")
private final String _reference;
@ColumnInfo(name = "timestamp")
private final long _timestamp;
@Ignore
public AuditLogEntry(@NonNull EventType eventType) {
this(eventType, null);
}
@Ignore
public AuditLogEntry(@NonNull EventType eventType, @Nullable String reference) {
_eventType = eventType;
_reference = reference;
_timestamp = System.currentTimeMillis();
}
AuditLogEntry(long id, @NonNull EventType eventType, @Nullable String reference, long timestamp) {
this.id = id;
_eventType = eventType;
_reference = reference;
_timestamp = timestamp;
}
public long getId() {
return id;
}
public EventType getEventType() {
return _eventType;
}
public String getReference() {
return _reference;
}
public long getTimestamp() {
return _timestamp;
}
}

View File

@ -0,0 +1,66 @@
package com.beemdevelopment.aegis.database;
import androidx.lifecycle.LiveData;
import com.beemdevelopment.aegis.EventType;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class AuditLogRepository {
private final AuditLogDao _auditLogDao;
private final Executor _executor;
public AuditLogRepository(AuditLogDao auditLogDao) {
_auditLogDao = auditLogDao;
_executor = Executors.newSingleThreadExecutor();
}
public LiveData<List<AuditLogEntry>> getAllAuditLogEntries() {
return _auditLogDao.getAll();
}
public void addVaultUnlockedEvent() {
AuditLogEntry auditLogEntry = new AuditLogEntry(EventType.VAULT_UNLOCKED);
insert(auditLogEntry);
}
public void addBackupCreatedEvent() {
AuditLogEntry auditLogEntry = new AuditLogEntry(EventType.VAULT_BACKUP_CREATED);
insert(auditLogEntry);
}
public void addAndroidBackupCreatedEvent() {
AuditLogEntry auditLogEntry = new AuditLogEntry(EventType.VAULT_ANDROID_BACKUP_CREATED);
insert(auditLogEntry);
}
public void addVaultExportedEvent() {
AuditLogEntry auditLogEntry = new AuditLogEntry(EventType.VAULT_EXPORTED);
insert(auditLogEntry);
}
public void addEntrySharedEvent(String reference) {
AuditLogEntry auditLogEntry = new AuditLogEntry(EventType.ENTRY_SHARED, reference);
insert(auditLogEntry);
}
public void addVaultUnlockFailedPasswordEvent() {
AuditLogEntry auditLogEntry = new AuditLogEntry(EventType.VAULT_UNLOCK_FAILED_PASSWORD);
insert(auditLogEntry);
}
public void addVaultUnlockFailedBiometricsEvent() {
AuditLogEntry auditLogEntry = new AuditLogEntry(EventType.VAULT_UNLOCK_FAILED_BIOMETRICS);
insert(auditLogEntry);
}
public void insert(AuditLogEntry auditLogEntry) {
_executor.execute(() -> {
_auditLogDao.insert(auditLogEntry);
});
}
}

View File

@ -21,6 +21,7 @@ import androidx.core.view.ViewPropertyAnimatorCompat;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.ThemeMap;
import com.beemdevelopment.aegis.database.AuditLogRepository;
import com.beemdevelopment.aegis.helpers.ThemeHelper;
import com.beemdevelopment.aegis.icons.IconPackManager;
import com.beemdevelopment.aegis.vault.VaultManager;
@ -48,6 +49,9 @@ public abstract class AegisActivity extends AppCompatActivity implements VaultMa
@Inject
protected VaultManager _vaultManager;
@Inject
protected AuditLogRepository _auditLogRepository;
@Inject
protected IconPackManager _iconPackManager;

View File

@ -344,6 +344,8 @@ public class AuthActivity extends AegisActivity {
finish(result.getKey(), result.isSlotRepaired());
} else {
_decryptButton.setEnabled(true);
_auditLogRepository.addVaultUnlockFailedPasswordEvent();
onInvalidPassword();
}
}
@ -356,6 +358,7 @@ public class AuthActivity extends AegisActivity {
_bioPrompt = null;
if (!BiometricsHelper.isCanceled(errorCode)) {
_auditLogRepository.addVaultUnlockFailedBiometricsEvent();
Toast.makeText(AuthActivity.this, errString, Toast.LENGTH_LONG).show();
}
}

View File

@ -522,6 +522,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
private void onDecryptResult() {
_auditLogRepository.addVaultUnlockedEvent();
loadEntries();
checkTimeSyncSetting();
}
@ -1176,6 +1178,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
for (VaultEntry entry : _selectedEntries) {
GoogleAuthInfo authInfo = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer());
authInfos.add(authInfo);
_auditLogRepository.addEntrySharedEvent(entry.getUUID().toString());
}
intent.putExtra("authInfos", authInfos);

View File

@ -0,0 +1,97 @@
package com.beemdevelopment.aegis.ui.fragments.preferences;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.database.AuditLogEntry;
import com.beemdevelopment.aegis.database.AuditLogRepository;
import com.beemdevelopment.aegis.helpers.MetricsHelper;
import com.beemdevelopment.aegis.ui.models.AuditLogEntryModel;
import com.beemdevelopment.aegis.ui.views.AuditLogAdapter;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.vault.VaultManager;
import java.util.List;
import java.util.UUID;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class AuditLogPreferencesFragment extends Fragment {
@Inject
AuditLogRepository _auditLogRepository;
private AuditLogAdapter _adapter;
private RecyclerView _auditLogRecyclerView;
private LinearLayout _noAuditLogsView;
@Inject
VaultManager _vaultManager;
public AuditLogPreferencesFragment() {
super(R.layout.fragment_audit_log);
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
LiveData<List<AuditLogEntry>> entries = _auditLogRepository.getAllAuditLogEntries();
_adapter = new AuditLogAdapter();
_noAuditLogsView = view.findViewById(R.id.vEmptyList);
_auditLogRecyclerView = view.findViewById(R.id.list_audit_log);
LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext());
_auditLogRecyclerView.addItemDecoration(new SpacesItemDecoration(8));
_auditLogRecyclerView.setLayoutManager(layoutManager);
_auditLogRecyclerView.setAdapter(_adapter);
_auditLogRecyclerView.setNestedScrollingEnabled(false);
entries.observe(getViewLifecycleOwner(), entries1 -> {
_noAuditLogsView.setVisibility(entries1.isEmpty() ? View.VISIBLE : View.GONE);
for (AuditLogEntry entry : entries1) {
VaultEntry referencedEntry = null;
if (entry.getReference() != null) {
referencedEntry = _vaultManager.getVault().getEntryByUUID(UUID.fromString(entry.getReference()));
}
AuditLogEntryModel auditLogEntryModel = new AuditLogEntryModel(entry, referencedEntry);
_adapter.addAuditLogEntryModel(auditLogEntryModel);
}
});
}
private class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private final int _space;
public SpacesItemDecoration(int dpSpace) {
_space = MetricsHelper.convertDpToPixels(getContext(), dpSpace);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.left = _space;
outRect.right = _space;
outRect.bottom = _space;
if (parent.getChildLayoutPosition(view) == 0) {
outRect.top = _space;
}
}
}
}

View File

@ -23,6 +23,7 @@ import androidx.preference.Preference;
import com.beemdevelopment.aegis.BuildConfig;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.database.AuditLogRepository;
import com.beemdevelopment.aegis.helpers.DropdownHelper;
import com.beemdevelopment.aegis.importers.DatabaseImporter;
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
@ -63,6 +64,7 @@ import java.util.UUID;
import java.util.stream.Collectors;
import javax.crypto.Cipher;
import javax.inject.Inject;
public class ImportExportPreferencesFragment extends PreferencesFragment {
// keep a reference to the type of database converter that was selected
@ -277,6 +279,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
.setType(getExportMimeType(getExportRequestCode(pos, encrypt)))
.putExtra(Intent.EXTRA_TITLE, fileInfo.toString());
_auditLogRepository.addVaultExportedEvent();
ActivityResultLauncher<Intent> resultLauncher = getExportRequestLauncher(pos, encrypt);
_vaultManager.fireIntentLauncher(this, intent, resultLauncher);
});
@ -320,7 +323,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
// if the user creates an export, hide the backup reminder
_prefs.setLatestExportTimeNow();
_auditLogRepository.addVaultExportedEvent();
Uri uri = FileProvider.getUriForFile(requireContext(), BuildConfig.FILE_PROVIDER_AUTHORITY, file);
Intent intent = new Intent(Intent.ACTION_SEND)
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
@ -559,7 +562,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
} else {
// if the user creates an export, hide the backup reminder
_prefs.setLatestExportTimeNow();
_auditLogRepository.addVaultExportedEvent();
Toast.makeText(requireContext(), getString(R.string.exported_vault), Toast.LENGTH_SHORT).show();
}
}

View File

@ -13,6 +13,7 @@ import androidx.preference.PreferenceFragmentCompat;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.database.AuditLogRepository;
import com.beemdevelopment.aegis.helpers.AnimationsHelper;
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
import com.beemdevelopment.aegis.vault.VaultManager;
@ -38,6 +39,9 @@ public abstract class PreferencesFragment extends PreferenceFragmentCompat {
@Inject
VaultManager _vaultManager;
@Inject
protected AuditLogRepository _auditLogRepository;
@Override
@CallSuper
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {

View File

@ -0,0 +1,24 @@
package com.beemdevelopment.aegis.ui.models;
import com.beemdevelopment.aegis.database.AuditLogEntry;
import com.beemdevelopment.aegis.vault.VaultEntry;
import javax.annotation.Nullable;
public class AuditLogEntryModel {
private AuditLogEntry _auditLogEntry;
private VaultEntry _referencedVaultEntry;
public AuditLogEntryModel(AuditLogEntry auditLogEntry, @Nullable VaultEntry referencedVaultEntry) {
_auditLogEntry = auditLogEntry;
_referencedVaultEntry = referencedVaultEntry;
}
public AuditLogEntry getAuditLogEntry() {
return _auditLogEntry;
}
public VaultEntry getReferencedVaultEntry() {
return _referencedVaultEntry;
}
}

View File

@ -0,0 +1,62 @@
package com.beemdevelopment.aegis.ui.views;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.database.AuditLogEntry;
import com.beemdevelopment.aegis.ui.models.AuditLogEntryModel;
import com.beemdevelopment.aegis.vault.VaultEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class AuditLogAdapter extends RecyclerView.Adapter<AuditLogHolder> {
private List<AuditLogEntryModel> _auditLogEntryModels;
private List<VaultEntry> _referencedEntries;
public AuditLogAdapter() {
_auditLogEntryModels = new ArrayList<>();
_referencedEntries = new ArrayList<>();
}
public void addAuditLogEntryModel(AuditLogEntryModel auditLogEntryModel) {
_auditLogEntryModels.add(auditLogEntryModel);
int position = getItemCount() - 1;
if (position == 0) {
notifyDataSetChanged();
} else {
notifyItemInserted(position);
}
}
public void addReferencedEntry(VaultEntry vaultEntry) {
_referencedEntries.add(vaultEntry);
}
@NonNull
@Override
public AuditLogHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_audit_log, parent, false);
return new AuditLogHolder(view);
}
@Override
public void onBindViewHolder(@NonNull AuditLogHolder holder, int position) {
AuditLogEntryModel auditLogEntryModel = _auditLogEntryModels.get(position);
VaultEntry referencedEntry = null;
holder.setData(auditLogEntryModel);
}
@Override
public int getItemCount() {
return _auditLogEntryModels.size();
}
}

View File

@ -0,0 +1,140 @@
package com.beemdevelopment.aegis.ui.views;
import android.content.Context;
import android.content.res.ColorStateList;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.EventType;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.database.AuditLogEntry;
import com.beemdevelopment.aegis.ui.models.AuditLogEntryModel;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.google.android.material.color.MaterialColors;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
public class AuditLogHolder extends RecyclerView.ViewHolder {
private final CardView _cardView;
private final ImageView _auditLogEntryIcon;
private final TextView _auditLogEntryTitle;
private final TextView _auditLogEntryDescription;
private final TextView _auditLogEntryReference;
private final TextView _auditLogEntryTimestamp;
private final int _errorBackgroundColor;
private final ColorStateList _initialBackgroundColor;
private final int _initialIconColor;
public AuditLogHolder(final View view) {
super(view);
_cardView = (CardView)view;
_auditLogEntryIcon = view.findViewById(R.id.iv_icon_view);
_auditLogEntryTitle = view.findViewById(R.id.text_audit_log_title);
_auditLogEntryDescription = view.findViewById(R.id.text_audit_log_description);
_auditLogEntryReference = view.findViewById(R.id.text_audit_log_reference);
_auditLogEntryTimestamp = view.findViewById(R.id.text_audit_log_timestamp);
_initialBackgroundColor = _cardView.getCardBackgroundColor();
_initialIconColor = MaterialColors.getColor(view, com.google.android.material.R.attr.colorTertiaryContainer);
_errorBackgroundColor = MaterialColors.getColor(view, com.google.android.material.R.attr.colorErrorContainer);
}
public void setData(AuditLogEntryModel auditLogEntryModel) {
AuditLogEntry auditLogEntry = auditLogEntryModel.getAuditLogEntry();
_auditLogEntryIcon.setImageResource(getIconResource(auditLogEntry.getEventType()));
_auditLogEntryTitle.setText(EventType.getEventTitleRes(auditLogEntry.getEventType()));
_auditLogEntryDescription.setText(getEventTypeDescriptionRes(auditLogEntry.getEventType()));
_auditLogEntryTimestamp.setText(formatTimestamp(_cardView.getContext(), auditLogEntry.getTimestamp()).toLowerCase());
if (auditLogEntryModel.getReferencedVaultEntry() != null) {
VaultEntry referencedVaultEntry = auditLogEntryModel.getReferencedVaultEntry();
_auditLogEntryReference.setText(String.format("%s (%s)", referencedVaultEntry.getIssuer(), referencedVaultEntry.getName()));
} else {
_auditLogEntryReference.setVisibility(View.GONE);
}
setCardBackgroundColor(auditLogEntry.getEventType());
}
private void setCardBackgroundColor(EventType eventType) {
if (eventType == EventType.VAULT_UNLOCK_FAILED_PASSWORD || eventType == EventType.VAULT_UNLOCK_FAILED_BIOMETRICS) {
_cardView.setCardBackgroundColor(_errorBackgroundColor);
_auditLogEntryIcon.setBackgroundColor(_errorBackgroundColor);
} else {
_cardView.setCardBackgroundColor(_initialBackgroundColor);
_auditLogEntryIcon.setBackgroundColor(_initialIconColor);
}
}
private int getEventTypeDescriptionRes(EventType eventType) {
switch (eventType) {
case VAULT_UNLOCKED:
return R.string.event_description_vault_unlocked;
case VAULT_BACKUP_CREATED:
return R.string.event_description_backup_created;
case VAULT_ANDROID_BACKUP_CREATED:
return R.string.event_description_android_backup_created;
case VAULT_EXPORTED:
return R.string.event_description_vault_exported;
case ENTRY_SHARED:
return R.string.event_description_entry_shared;
case VAULT_UNLOCK_FAILED_PASSWORD:
return R.string.event_description_vault_unlock_failed_password;
case VAULT_UNLOCK_FAILED_BIOMETRICS:
return R.string.event_description_vault_unlock_failed_biometrics;
default:
return R.string.event_unknown;
}
}
private int getIconResource(EventType eventType) {
switch(eventType) {
case VAULT_UNLOCKED:
return R.drawable.ic_lock_open;
case VAULT_BACKUP_CREATED:
case VAULT_ANDROID_BACKUP_CREATED:
return R.drawable.ic_folder_zip;
case VAULT_EXPORTED:
return R.drawable.ic_export_notes;
case ENTRY_SHARED:
return R.drawable.ic_share;
case VAULT_UNLOCK_FAILED_PASSWORD:
case VAULT_UNLOCK_FAILED_BIOMETRICS:
return R.drawable.ic_lock;
}
return -1;
}
private static String formatTimestamp(Context context, long epochMilli) {
LocalDateTime now = LocalDateTime.now();
LocalDateTime timestamp = LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneId.systemDefault());
long daysBetween = ChronoUnit.DAYS.between(timestamp.toLocalDate(), now.toLocalDate());
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("h:mm a");
if (daysBetween < 1) {
String formattedTime = timestamp.format(timeFormatter);
return context.getString(R.string.today_at_time, formattedTime);
} else if (daysBetween < 7) {
DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE");
String dayOfWeek = timestamp.format(dayOfWeekFormatter);
String formattedTime = timestamp.format(timeFormatter);
return context.getString(R.string.day_of_week_at_time, dayOfWeek, formattedTime);
} else {
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(context.getString(R.string.date_format));
return timestamp.format(dateFormatter);
}
}
}

View File

@ -10,6 +10,7 @@ import androidx.annotation.NonNull;
import androidx.documentfile.provider.DocumentFile;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.database.AuditLogRepository;
import com.beemdevelopment.aegis.util.IOUtils;
import java.io.File;
@ -41,17 +42,20 @@ public class VaultBackupManager {
private final Context _context;
private final Preferences _prefs;
private final ExecutorService _executor;
private final AuditLogRepository _auditLogRepository;
public VaultBackupManager(Context context) {
public VaultBackupManager(Context context, AuditLogRepository auditLogRepository) {
_context = context;
_prefs = new Preferences(context);
_executor = Executors.newSingleThreadExecutor();
_auditLogRepository = auditLogRepository;
}
public void scheduleBackup(File tempFile, Uri dirUri, int versionsToKeep) {
_executor.execute(() -> {
try {
createBackup(tempFile, dirUri, versionsToKeep);
_auditLogRepository.addBackupCreatedEvent();
_prefs.setBuiltInBackupResult(new Preferences.BackupResult(null));
} catch (VaultRepositoryException | VaultBackupPermissionException e) {
e.printStackTrace();

View File

@ -15,6 +15,7 @@ import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.database.AuditLogRepository;
import com.beemdevelopment.aegis.services.NotificationService;
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
@ -37,12 +38,15 @@ public class VaultManager {
private final List<LockListener> _lockListeners;
private boolean _blockAutoLock;
public VaultManager(@NonNull Context context) {
private final AuditLogRepository _auditLogRepository;
public VaultManager(@NonNull Context context, AuditLogRepository auditLogRepository) {
_context = context;
_prefs = new Preferences(_context);
_backups = new VaultBackupManager(_context);
_backups = new VaultBackupManager(_context, auditLogRepository);
_androidBackups = new BackupManager(context);
_lockListeners = new ArrayList<>();
_auditLogRepository = auditLogRepository;
}
/**

View File

@ -0,0 +1,5 @@
<vector android:alpha="0.5" android:height="24dp"
android:viewportHeight="960" android:viewportWidth="960"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="m648,820 l112,-112v92h40v-160L640,640v40h92L620,792l28,28ZM200,840q-33,0 -56.5,-23.5T120,760v-560q0,-33 23.5,-56.5T200,120h560q33,0 56.5,23.5T840,200v268q-19,-9 -39,-15.5t-41,-9.5v-243L200,200v560h242q3,22 9.5,42t15.5,38L200,840ZM200,720v40,-560 243,-3 280ZM280,680h163q3,-21 9.5,-41t14.5,-39L280,600v80ZM280,520h244q32,-30 71.5,-50t84.5,-27v-3L280,440v80ZM280,360h400v-80L280,280v80ZM720,920q-83,0 -141.5,-58.5T520,720q0,-83 58.5,-141.5T720,520q83,0 141.5,58.5T920,720q0,83 -58.5,141.5T720,920Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:alpha="0.5" android:height="24dp"
android:viewportHeight="960" android:viewportWidth="960"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M640,480v-80h80v80h-80ZM640,560h-80v-80h80v80ZM640,640v-80h80v80h-80ZM447,320l-80,-80L160,240v480h400v-80h80v80h160v-400L640,320v80h-80v-80L447,320ZM160,800q-33,0 -56.5,-23.5T80,720v-480q0,-33 23.5,-56.5T160,160h240l80,80h320q33,0 56.5,23.5T880,320v400q0,33 -23.5,56.5T800,800L160,800ZM160,720v-480,480Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:alpha="0.5" android:height="24dp"
android:viewportHeight="960" android:viewportWidth="960"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M240,880q-33,0 -56.5,-23.5T160,800v-400q0,-33 23.5,-56.5T240,320h40v-80q0,-83 58.5,-141.5T480,40q83,0 141.5,58.5T680,240v80h40q33,0 56.5,23.5T800,400v400q0,33 -23.5,56.5T720,880L240,880ZM240,800h480v-400L240,400v400ZM480,680q33,0 56.5,-23.5T560,600q0,-33 -23.5,-56.5T480,520q-33,0 -56.5,23.5T400,600q0,33 23.5,56.5T480,680ZM360,320h240v-80q0,-50 -35,-85t-85,-35q-50,0 -85,35t-35,85v80ZM240,800v-400,400Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:alpha="0.5" android:height="24dp"
android:viewportHeight="960" android:viewportWidth="960"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M240,320h360v-80q0,-50 -35,-85t-85,-35q-50,0 -85,35t-35,85h-80q0,-83 58.5,-141.5T480,40q83,0 141.5,58.5T680,240v80h40q33,0 56.5,23.5T800,400v400q0,33 -23.5,56.5T720,880L240,880q-33,0 -56.5,-23.5T160,800v-400q0,-33 23.5,-56.5T240,320ZM240,800h480v-400L240,400v400ZM480,680q33,0 56.5,-23.5T560,600q0,-33 -23.5,-56.5T480,520q-33,0 -56.5,23.5T400,600q0,33 23.5,56.5T480,680ZM240,800v-400,400Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:alpha="0.5" android:height="24dp"
android:viewportHeight="960" android:viewportWidth="960"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M720,880q-50,0 -85,-35t-35,-85q0,-7 1,-14.5t3,-13.5L322,568q-17,15 -38,23.5t-44,8.5q-50,0 -85,-35t-35,-85q0,-50 35,-85t85,-35q23,0 44,8.5t38,23.5l282,-164q-2,-6 -3,-13.5t-1,-14.5q0,-50 35,-85t85,-35q50,0 85,35t35,85q0,50 -35,85t-85,35q-23,0 -44,-8.5T638,288L356,452q2,6 3,13.5t1,14.5q0,7 -1,14.5t-3,13.5l282,164q17,-15 38,-23.5t44,-8.5q50,0 85,35t35,85q0,50 -35,85t-85,35ZM720,240q17,0 28.5,-11.5T760,200q0,-17 -11.5,-28.5T720,160q-17,0 -28.5,11.5T680,200q0,17 11.5,28.5T720,240ZM240,520q17,0 28.5,-11.5T280,480q0,-17 -11.5,-28.5T240,440q-17,0 -28.5,11.5T200,480q0,17 11.5,28.5T240,520ZM720,800q17,0 28.5,-11.5T760,760q0,-17 -11.5,-28.5T720,720q-17,0 -28.5,11.5T680,760q0,17 11.5,28.5T720,800ZM720,200ZM240,480ZM720,760Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M120,720Q87,720 63.5,696.5Q40,673 40,640Q40,607 63.5,583.5Q87,560 120,560Q126,560 130.5,560Q135,560 140,562L322,380Q320,375 320,370.5Q320,366 320,360Q320,327 343.5,303.5Q367,280 400,280Q433,280 456.5,303.5Q480,327 480,360Q480,362 478,380L580,482Q585,480 589.5,480Q594,480 600,480Q606,480 610.5,480Q615,480 620,482L762,340Q760,335 760,330.5Q760,326 760,320Q760,287 783.5,263.5Q807,240 840,240Q873,240 896.5,263.5Q920,287 920,320Q920,353 896.5,376.5Q873,400 840,400Q834,400 829.5,400Q825,400 820,398L678,540Q680,545 680,549.5Q680,554 680,560Q680,593 656.5,616.5Q633,640 600,640Q567,640 543.5,616.5Q520,593 520,560Q520,554 520,549.5Q520,545 522,540L420,438Q415,440 410.5,440Q406,440 400,440Q398,440 380,438L198,620Q200,625 200,629.5Q200,634 200,640Q200,673 176.5,696.5Q153,720 120,720Z"/>
</vector>

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
app:cardElevation="0dp"
style="@style/Widget.Aegis.EntryCardView">
<LinearLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center_vertical">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingTop="12dp"
android:paddingStart="14dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/iv_icon_view"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_alignParentStart="true"
android:background="?attr/colorTertiaryContainer"
android:cropToPadding="true"
android:padding="2dp"
android:scaleType="centerInside"
android:src="@drawable/ic_lock_open"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Aegis.ImageView.Rounded" />
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="bottom"
android:id="@+id/relativeLayout"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/description">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/text_audit_log_title"
android:text="@string/issuer"
android:textStyle="bold"
android:includeFontPadding="false"
android:textSize="16sp"
android:ellipsize="end"
android:maxLines="1"/>
</RelativeLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/text_audit_log_description"
android:layoutDirection="ltr"
android:layout_below="@id/description"
android:includeFontPadding="false"
tools:text="The vault has been succesfully unlocked"
android:layout_alignParentStart="true"
android:layout_marginTop="4dp"
android:textStyle="normal"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/text_audit_log_reference"
android:layoutDirection="ltr"
android:layout_below="@id/text_audit_log_description"
android:includeFontPadding="false"
tools:text="Aegis (authenticator)"
android:layout_alignParentStart="true"
android:layout_marginTop="4dp"
android:textStyle="italic"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/text_audit_log_timestamp"
android:layoutDirection="ltr"
android:layout_below="@id/text_audit_log_reference"
android:includeFontPadding="false"
tools:text="Today at 5:59 PM"
android:textSize="12sp"
android:layout_alignParentStart="true"
android:textColor="?attr/colorOutline"
android:layout_marginTop="8dp"
android:textStyle="normal"/>
</RelativeLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center|fill_vertical"
android:visibility="gone"
android:id="@+id/vEmptyList"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="150dp">
<ImageView
android:id="@+id/imageView"
android:layout_width="75dp"
android:layout_height="75dp"
android:src="@drawable/ic_timeline_24" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_events_title"
android:paddingTop="17dp"
android:textSize="18sp" />
<TextView
android:id="@+id/txt_no_audit_logs"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:lineSpacingExtra="5dp"
android:paddingTop="7dp"
android:text="@string/no_events_description"
android:textAlignment="center" />
</LinearLayout>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_audit_log"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:scrollbarStyle="outsideOverlay"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -31,6 +31,8 @@
<string name="pref_section_security_summary">Configure encryption, biometric unlock, auto lock and other security settings.</string>
<string name="pref_section_import_export_title">Import &amp; Export</string>
<string name="pref_section_import_export_summary">Import backups of Aegis or other authenticator apps. Create manual exports of your Aegis vault.</string>
<string name="pref_section_audit_log_title">Audit log</string>
<string name="pref_section_audit_log_summary">Find a list of all reported important events that happened within the app.</string>
<string name="pref_section_backups_title">Backups</string>
<string name="pref_section_backups_summary">Set up automatic backups to a location of your choosing or enable participation in Android\'s cloud backup system.</string>
<string name="pref_section_icon_packs">Icon packs</string>
@ -106,6 +108,28 @@
<string name="pref_set_password_title">Change password</string>
<string name="pref_set_password_summary">Set a new password which you will need to unlock your vault</string>
<string name="no_events_title">No reported events</string>
<string name="no_events_description">No important events have been reported within the app</string>
<string name="event_title_vault_unlocked">Vault unlocked</string>
<string name="event_description_vault_unlocked">The vault has been successfully unlocked</string>
<string name="event_title_backup_created">Backup created</string>
<string name="event_description_backup_created">A backup of the vault has been successfully created</string>
<string name="event_title_android_backup_created">Backup created by Android</string>
<string name="event_description_android_backup_created">A backup of the vault has been successfully created by Android</string>
<string name="event_title_vault_exported">Vault exported</string>
<string name="event_description_vault_exported">A copy of the vault has been exported</string>
<string name="event_title_entry_shared">Entry shared</string>
<string name="event_description_entry_shared">An entry was shared</string>
<string name="event_title_vault_unlock_failed_password">Vault unlock failed (password)</string>
<string name="event_description_vault_unlock_failed_password">An attempt to unlock the vault with a password failed</string>
<string name="event_title_vault_unlock_failed_biometrics">Vault unlock failed (biometrics)</string>
<string name="event_description_vault_unlock_failed_biometrics">An attempt to unlock the vault with biometrics failed</string>
<string name="event_unknown">Unknown event type</string>
<string name="today_at_time">Today at %1$s</string>
<string name="day_of_week_at_time">%1$s at %2$s</string>
<string name="date_format">dd/MM/yyyy</string>
<string name="export_encrypted">Encrypt the vault</string>
<string name="export_help">This action will export the vault out of Aegis\' internal storage. Select the format you\'d like your export to be in:</string>
<string name="export_warning_unencrypted">You are about to export an unencrypted copy of your Aegis vault. <b>This is not recommended</b>.</string>

View File

@ -258,4 +258,9 @@
<style name="Widget.Aegis.AlertDialog.Icon.Warning" parent="@style/MaterialAlertDialog.Material3.Title.Icon.CenterStacked">
<item name="android:tint">?attr/colorError</item>
</style>
<style name="ShapeAppearanceOverlay.Aegis.ImageView.Rounded" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">8dp</item>
</style>
</resources>

View File

@ -40,4 +40,10 @@
app:title="@string/pref_section_import_export_title"
app:summary="@string/pref_section_import_export_summary" />
<Preference
android:fragment="com.beemdevelopment.aegis.ui.fragments.preferences.AuditLogPreferencesFragment"
app:icon="@drawable/ic_timeline_24"
app:title="@string/pref_section_audit_log_title"
app:summary="@string/pref_section_audit_log_summary" />
</androidx.preference.PreferenceScreen>