Compare commits

..

17 Commits

Author SHA1 Message Date
akwizgran
0703548e97 Use ACTION_GET_CONTENT to show more content providers. 2021-03-10 16:05:58 +00:00
Torsten Grote
c5d2661c1d Merge branch '1919-password-fields-not-focusable' into 'master'
Condition display of progressbar on a isCreatingAccount LiveData

Closes #1819 and #1919

See merge request briar/briar!1355
2021-03-03 13:10:29 +00:00
akwizgran
629cff20a3 Merge branch '1952-oom-avatar-preview-glide' into 'master'
Load avatar previews with Glide to prevent OOM errors

Closes #1952

See merge request briar/briar!1388
2021-03-01 18:02:19 +00:00
Torsten Grote
6cfb70db95 Load image from URI with Glide to prevent OOM errors 2021-03-01 14:15:53 -03:00
Torsten Grote
737ecfb620 Some unrelated code changes to avatar settings 2021-03-01 14:15:08 -03:00
akwizgran
5a424b178e Merge branch '1667-toolbar-options' into 'master'
Make group/create forum/write blog post buttons to always show

Closes #1667

See merge request briar/briar!1377
2021-03-01 16:34:14 +00:00
Torsten Grote
59f4e7c34a Super call to onRequestPermissionsResult() is now required 2021-02-23 10:55:20 -03:00
Torsten Grote
2480824d69 Fix toolbar buttons not showing up after sign-in on lower API levels 2021-02-23 10:55:20 -03:00
akwizgran
a6c2000d81 Merge branch '1825-pending-contact-error' into 'master'
Be more specific about errors when adding pending contact

Closes #1825

See merge request briar/briar!1354
2021-02-22 11:12:49 +00:00
akwizgran
a38a3139d9 Merge branch 'fix-message-in-profile-picture-confirmation' into 'master'
Fix message in profile picture confirmation

See merge request briar/briar!1356
2021-02-22 11:06:58 +00:00
akwizgran
4c8adaa02b Merge branch '1399-unlock-activity-crash' into 'master'
Let LockManager only lock current, not future process

Closes #1399

See merge request briar/briar!1374
2021-02-22 10:49:17 +00:00
Torsten Grote
a546fecc01 Let LockManager only lock current, not future process
This fixes a bug on Android 8
where the AlarmManager would re-start a killed BriarService.
Then the LockManager lingers around locked and causes an ANR on Android 8.x when the user comes back to it.
2021-02-19 10:42:43 -03:00
Nico Alt
3e7e37f5f6 Include pending contact id in error response 2021-02-19 12:00:00 +00:00
Nico Alt
d095ba0b15 Include name/alias of already existing (pending) contact in error 2021-02-19 14:44:56 +01:00
Nico Alt
7fab97d26c Be more specific about errors when adding pending contact
Following the docs at
https://code.briarproject.org/briar/briar/-/blob/beta-1.2.14/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java#L110

Fixes #1825
2021-02-19 14:44:56 +01:00
Daniel Lublin
6b61725c6a Condition display of progressbar on a isCreatingAccount LiveData
Avoiding the mess with saving onSaveInstanceState, and the (in this
case) unwanted restoring of it upon back-button tap.

Closes #1919

Test instructions:

- Precondition: fresh install, setting up a new account
  - Testing specific bug fix:
    - Choose a name, tap next
    - Choose a password, tap next
      - Not testable on some devices which display "Create account" instead of "Next"
    - You are now on Background connections screen
    - Tap Back-button ◁
    - Ensure that password can be changed again
  - During setup process, rotate device and ensure that:
    - entered text is kept
    - progressbar is continuously displayed
2021-02-17 13:57:08 +01:00
Sebastian Kürten
e4a66615a7 Fix remark in dialog for confirming profile picture 2021-02-04 18:43:32 +01:00
24 changed files with 356 additions and 163 deletions

View File

@@ -95,6 +95,7 @@ dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
implementation project(':bramble-android')
implementation 'androidx.fragment:fragment:1.3.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.exifinterface:exifinterface:1.3.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

View File

@@ -46,6 +46,7 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Process.myPid;
import static androidx.core.app.NotificationCompat.VISIBILITY_SECRET;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@@ -59,6 +60,7 @@ import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGO
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
import static org.briarproject.briar.api.android.LockManager.ACTION_LOCK;
import static org.briarproject.briar.api.android.LockManager.EXTRA_PID;
public class BriarService extends Service {
@@ -210,7 +212,12 @@ public class BriarService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (ACTION_LOCK.equals(intent.getAction())) {
lockManager.setLocked(true);
int pid = intent.getIntExtra(EXTRA_PID, -1);
if (pid == myPid()) lockManager.setLocked(true);
else if (LOG.isLoggable(WARNING)) {
LOG.warning("Tried to lock process " + pid + " but this is " +
myPid());
}
}
return START_NOT_STICKY; // Don't restart automatically if killed
}

View File

@@ -33,7 +33,6 @@ public class DozeFragment extends SetupFragment
private DozeView dozeView;
private HuaweiView huaweiView;
private Button next;
private ProgressBar progressBar;
private boolean secondAttempt = false;
public static DozeFragment newInstance() {
@@ -58,11 +57,19 @@ public class DozeFragment extends SetupFragment
huaweiView = v.findViewById(R.id.huaweiView);
huaweiView.setOnCheckedChangedListener(this);
next = v.findViewById(R.id.next);
progressBar = v.findViewById(R.id.progress);
ProgressBar progressBar = v.findViewById(R.id.progress);
dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
next.setOnClickListener(this);
viewModel.getIsCreatingAccount()
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
if (isCreatingAccount) {
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
});
return v;
}
@@ -104,15 +111,6 @@ public class DozeFragment extends SetupFragment
@Override
public void onClick(View view) {
setNextClicked();
viewModel.dozeExceptionConfirmed();
}
@Override
void setNextClicked() {
super.setNextClicked();
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
}

View File

@@ -32,8 +32,10 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.app.PendingIntent.getService;
import static android.content.Context.ALARM_SERVICE;
import static android.os.Process.myPid;
import static android.os.SystemClock.elapsedRealtime;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.WARNING;
@@ -75,23 +77,25 @@ public class LockManagerImpl implements LockManager, Service, EventListener {
LockManagerImpl(Application app, SettingsManager settingsManager,
AndroidNotificationManager notificationManager,
@DatabaseExecutor Executor dbExecutor) {
this.appContext = app.getApplicationContext();
appContext = app.getApplicationContext();
this.settingsManager = settingsManager;
this.notificationManager = notificationManager;
this.dbExecutor = dbExecutor;
this.alarmManager =
alarmManager =
(AlarmManager) appContext.getSystemService(ALARM_SERVICE);
Intent i =
new Intent(ACTION_LOCK, null, appContext, BriarService.class);
this.lockIntent = getService(appContext, 0, i, 0);
this.timeoutNever = Integer.valueOf(
i.putExtra(EXTRA_PID, myPid());
// When not using FLAG_UPDATE_CURRENT, the intent might have no extras
lockIntent = getService(appContext, 0, i, FLAG_UPDATE_CURRENT);
timeoutNever = Integer.parseInt(
appContext.getString(R.string.pref_lock_timeout_value_never));
this.timeoutDefault = Integer.valueOf(
timeoutDefault = Integer.parseInt(
appContext.getString(R.string.pref_lock_timeout_value_default));
this.timeoutMinutes = timeoutNever;
timeoutMinutes = timeoutNever;
// setting this in the constructor makes #getValue() @NonNull
this.lockable.setValue(false);
lockable.setValue(false);
}
@Override
@@ -148,7 +152,7 @@ public class LockManagerImpl implements LockManager, Service, EventListener {
boolean oldValue = lockable.getValue();
boolean newValue = hasScreenLock(appContext) && lockableSetting;
if (oldValue != newValue) {
this.lockable.setValue(newValue);
lockable.setValue(newValue);
}
}

View File

@@ -38,7 +38,6 @@ public class SetPasswordFragment extends SetupFragment {
private TextInputEditText passwordConfirmation;
private StrengthMeter strengthMeter;
private Button nextButton;
private ProgressBar progressBar;
public static SetPasswordFragment newInstance() {
return new SetPasswordFragment();
@@ -64,7 +63,7 @@ public class SetPasswordFragment extends SetupFragment {
v.findViewById(R.id.password_confirm_wrapper);
passwordConfirmation = v.findViewById(R.id.password_confirm);
nextButton = v.findViewById(R.id.next);
progressBar = v.findViewById(R.id.progress);
ProgressBar progressBar = v.findViewById(R.id.progress);
passwordEntry.addTextChangedListener(this);
passwordConfirmation.addTextChangedListener(this);
@@ -75,6 +74,17 @@ public class SetPasswordFragment extends SetupFragment {
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
}
viewModel.getIsCreatingAccount()
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
if (isCreatingAccount) {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
// this also avoids the keyboard popping up
passwordEntry.setFocusable(false);
passwordConfirmation.setFocusable(false);
}
});
return v;
}
@@ -116,20 +126,6 @@ public class SetPasswordFragment extends SetupFragment {
IBinder token = passwordEntry.getWindowToken();
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
setNextClicked();
viewModel.setPassword(passwordEntry.getText().toString());
}
@Override
void setNextClicked() {
super.setNextClicked();
passwordEntry.setFocusable(false);
passwordConfirmation.setFocusable(false);
if (!viewModel.needToShowDozeFragment()) {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
}
}

View File

@@ -7,7 +7,6 @@ import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -19,8 +18,6 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
@@ -35,7 +32,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
OnEditorActionListener, OnClickListener {
private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
private boolean clicked = false;
@Inject
ViewModelProvider.Factory viewModelFactory;
@@ -48,27 +44,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
.get(SetupViewModel.class);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
clicked = savedInstanceState.getBoolean(STATE_KEY_CLICKED);
}
if (clicked) {
setNextClicked();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_KEY_CLICKED, clicked);
}
@CallSuper
void setNextClicked() {
this.clicked = true;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.help_action, menu);
@@ -114,5 +89,4 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
public void afterTextChanged(Editable editable) {
// noop
}
}

View File

@@ -17,6 +17,8 @@ import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
@@ -36,6 +38,8 @@ class SetupViewModel extends AndroidViewModel {
@Nullable
private String authorName, password;
private final MutableLiveEvent<State> state = new MutableLiveEvent<>();
private final MutableLiveData<Boolean> isCreatingAccount =
new MutableLiveData<>(false);
private final AccountManager accountManager;
private final Executor ioExecutor;
@@ -67,6 +71,10 @@ class SetupViewModel extends AndroidViewModel {
return state;
}
LiveData<Boolean> getIsCreatingAccount() {
return isCreatingAccount;
}
void setAuthorName(String authorName) {
this.authorName = authorName;
state.setEvent(SET_PASSWORD);
@@ -97,6 +105,7 @@ class SetupViewModel extends AndroidViewModel {
private void createAccount() {
if (authorName == null) throw new IllegalStateException();
if (password == null) throw new IllegalStateException();
isCreatingAccount.setValue(true);
ioExecutor.execute(() -> {
if (accountManager.createAccount(authorName, password)) {
LOG.info("Created account");

View File

@@ -77,7 +77,7 @@ public class UnlockActivity extends BaseActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
@Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_KEYGUARD_UNLOCK) {
if (resultCode == RESULT_OK) unlock();

View File

@@ -34,7 +34,6 @@ import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
@MethodsNotNullByDefault
@@ -102,7 +101,8 @@ public class ContactListFragment extends BaseFragment
.observe(getViewLifecycleOwner(), result -> {
result.onError(this::handleException).onSuccess(items -> {
adapter.submitList(items);
if (requireNonNull(items).size() == 0) list.showData();
// TODO remove when BriarRecyclerView was adapted
list.showData();
});
});
viewModel.getHasPendingContacts()

View File

@@ -10,9 +10,8 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.DestroyableContext;
import org.briarproject.briar.android.activity.ActivityComponent;
import javax.annotation.Nullable;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
@@ -47,13 +46,11 @@ public abstract class BaseFragment extends Fragment
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
listener.onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
if (item.getItemId() == android.R.id.home) {
requireActivity().onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@UiThread
@@ -79,6 +76,7 @@ public abstract class BaseFragment extends Fragment
void handleException(Exception e);
}
@Deprecated
@CallSuper
@Override
public void runOnUiThreadUnlessDestroyed(Runnable r) {

View File

@@ -411,6 +411,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
@UiThread
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions,
grantResults);
if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION)
throw new AssertionError();
if (gotPermission(CAMERA, permissions, grantResults)) {

View File

@@ -49,14 +49,12 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
@@ -67,9 +65,7 @@ import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static androidx.core.view.GravityCompat.START;
import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
import static androidx.lifecycle.Lifecycle.State.STARTED;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
@@ -146,7 +142,7 @@ public class NavDrawerActivity extends BriarActivity implements
if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
});
Toolbar toolbar = findViewById(R.id.toolbar);
Toolbar toolbar = setUpCustomToolbar(false);
drawerLayout = findViewById(R.id.drawer_layout);
navigation = findViewById(R.id.navigation);
GridView transportsView = findViewById(R.id.transportsView);
@@ -156,11 +152,6 @@ public class NavDrawerActivity extends BriarActivity implements
startActivity(new Intent(this, TransportsActivity.class));
});
setSupportActionBar(toolbar);
ActionBar actionBar = requireNonNull(getSupportActionBar());
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
R.string.nav_drawer_open_description,
R.string.nav_drawer_close_description) {
@@ -184,9 +175,6 @@ public class NavDrawerActivity extends BriarActivity implements
if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) {
showSignOutFragment();
} else if (state == null) {
startFragment(ContactListFragment.newInstance(),
R.id.nav_btn_contacts);
}
if (state == null) {
// do not call this again when there's existing state
@@ -276,7 +264,6 @@ public class NavDrawerActivity extends BriarActivity implements
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
drawerLayout.closeDrawer(START);
clearBackStack();
if (item.getItemId() == R.id.nav_btn_lock) {
lockManager.setLocked(true);
ActivityCompat.finishAfterTransition(this);
@@ -296,8 +283,8 @@ public class NavDrawerActivity extends BriarActivity implements
FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag(SignOutFragment.TAG) != null) {
finish();
} else if (fm.getBackStackEntryCount() == 0
&& fm.findFragmentByTag(ContactListFragment.TAG) == null) {
} else if (fm.getBackStackEntryCount() == 0 &&
fm.findFragmentByTag(ContactListFragment.TAG) == null) {
// don't start fragments in the wrong part of lifecycle (#1904)
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) {
LOG.warning("Tried to start contacts fragment in state " +
@@ -346,30 +333,12 @@ public class NavDrawerActivity extends BriarActivity implements
startFragment(fragment);
}
private void startFragment(BaseFragment fragment) {
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
startFragment(fragment, false);
else startFragment(fragment, true);
}
private void startFragment(BaseFragment fragment,
boolean isAddedToBackStack) {
FragmentTransaction trans =
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.fade_in,
R.anim.fade_out, R.anim.fade_in,
R.anim.fade_out)
.replace(R.id.fragmentContainer, fragment,
fragment.getUniqueTag());
if (isAddedToBackStack) {
trans.addToBackStack(fragment.getUniqueTag());
}
trans.commit();
}
private void clearBackStack() {
getSupportFragmentManager().popBackStackImmediate(null,
POP_BACK_STACK_INCLUSIVE);
private void startFragment(BaseFragment f) {
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out,
R.anim.fade_in, R.anim.fade_out)
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
.commit();
}
@Override

View File

@@ -9,10 +9,13 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.conversation.glide.GlideApp;
import javax.inject.Inject;
@@ -32,7 +35,7 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
@Inject
ViewModelProvider.Factory viewModelFactory;
private SettingsViewModel settingsViewModel;
private SettingsViewModel viewModel;
private static final String ARG_URI = "uri";
private Uri uri;
@@ -51,6 +54,9 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
public void onAttach(Context ctx) {
super.onAttach(ctx);
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
ViewModelProvider provider =
new ViewModelProvider(requireActivity(), viewModelFactory);
viewModel = provider.get(SettingsViewModel.class);
}
@Override
@@ -60,32 +66,34 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
uri = Uri.parse(argUri);
FragmentActivity activity = requireActivity();
ViewModelProvider provider =
new ViewModelProvider(activity, viewModelFactory);
settingsViewModel = provider.get(SettingsViewModel.class);
AlertDialog.Builder builder =
new AlertDialog.Builder(activity, R.style.BriarDialogTheme);
LayoutInflater inflater = LayoutInflater.from(getContext());
LayoutInflater inflater = LayoutInflater.from(activity);
final View view =
inflater.inflate(R.layout.fragment_confirm_avatar_dialog, null);
builder.setView(view);
builder.setTitle(R.string.dialog_confirm_profile_picture_title);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.change,
(dialog, id) -> settingsViewModel.setAvatar(uri));
ImageView imageView = view.findViewById(R.id.image);
imageView.setImageURI(uri);
TextView textViewUserName = view.findViewById(R.id.username);
settingsViewModel.getOwnIdentityInfo().observe(activity,
us -> textViewUserName.setText(us.getLocalAuthor().getName()));
return builder.create();
GlideApp.with(imageView)
.load(uri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.error(R.drawable.ic_image_broken)
.into(imageView)
.waitForLayout();
// we can't use getViewLifecycleOwner() here
// as this fragment technically doesn't have a view
viewModel.getOwnIdentityInfo().observe(activity, us ->
textViewUserName.setText(us.getLocalAuthor().getName())
);
int theme = R.style.BriarDialogTheme;
return new AlertDialog.Builder(activity, theme)
.setView(view)
.setTitle(R.string.dialog_confirm_profile_picture_title)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.change, (d, id) ->
viewModel.setAvatar(uri)
)
.create();
}
}

View File

@@ -79,7 +79,7 @@ class SettingsViewModel extends AndroidViewModel {
return ownIdentityInfo;
}
public LiveEvent<Boolean> getSetAvatarFailed() {
LiveEvent<Boolean> getSetAvatarFailed() {
return setAvatarFailed;
}

View File

@@ -59,7 +59,6 @@ import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Context.POWER_SERVICE;
import static android.content.Intent.ACTION_GET_CONTENT;
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.CATEGORY_OPENABLE;
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
@@ -258,8 +257,9 @@ public class UiUtils {
}
public static Intent createSelectImageIntent(boolean allowMultiple) {
Intent intent = new Intent(SDK_INT >= 19 ?
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
// ACTION_GET_CONTENT returns more content providers than
// ACTION_OPEN_DOCUMENT (https://stackoverflow.com/questions/27568139/)
Intent intent = new Intent(ACTION_GET_CONTENT);
intent.setType("image/*");
intent.addCategory(CATEGORY_OPENABLE);
if (SDK_INT >= 19)

View File

@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
public interface LockManager {
String ACTION_LOCK = "lock";
String EXTRA_PID = "PID";
/**
* Stops the inactivity timer when the user interacts with the app.

View File

@@ -52,16 +52,17 @@
android:layout_height="0dp"
android:contentDescription="@string/close"
android:scaleType="center"
app:srcCompat="@drawable/ic_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_close"
app:tint="@color/briar_text_tertiary_inverse" />
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainer"
android:name="org.briarproject.briar.android.contact.ContactListFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"

View File

@@ -5,27 +5,28 @@
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_btn_contacts"
android:checked="true"
android:icon="@drawable/ic_contacts"
android:title="@string/contact_list_button"/>
android:title="@string/contact_list_button" />
<item
android:id="@+id/nav_btn_groups"
android:icon="@drawable/ic_group"
android:title="@string/groups_button"/>
android:title="@string/groups_button" />
<item
android:id="@+id/nav_btn_forums"
android:icon="@drawable/ic_forums_black_24dp"
android:title="@string/forums_button"/>
android:title="@string/forums_button" />
<item
android:id="@+id/nav_btn_blogs"
android:icon="@drawable/blogs"
android:title="@string/blogs_button"/>
android:title="@string/blogs_button" />
</group>
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_btn_settings"
android:icon="@drawable/ic_settings_black"
android:title="@string/settings_button"/>
android:title="@string/settings_button" />
<item
android:id="@+id/nav_btn_lock"
android:icon="@drawable/startup_lock"
@@ -35,7 +36,7 @@
<item
android:id="@+id/nav_btn_signout"
android:icon="@drawable/ic_signout"
android:title="@string/sign_out_button"/>
android:title="@string/sign_out_button" />
</group>
</menu>

View File

@@ -456,7 +456,7 @@
<!-- Settings Profile Picture -->
<string name="change_profile_picture">Tap to change your profile picture</string>
<string name="dialog_confirm_profile_picture_title">Change profile picture</string>
<string name="dialog_confirm_profile_picture_remark">Only your contacts can see your profile image</string>
<string name="dialog_confirm_profile_picture_remark">Only your contacts can see this picture</string>
<string name="change_profile_picture_failed_message">We\'re sorry, but something went wrong while updating your profile picture</string>
<!-- Settings Display -->

View File

@@ -1,7 +1,7 @@
dependencyVerification {
verify = [
'androidx.activity:activity-ktx:1.1.0:activity-ktx-1.1.0.aar:1996c36d3d2d62db5020b8ec634b5f854b1a698960c3552e1a00c69221baeabe',
'androidx.activity:activity:1.1.0:activity-1.1.0.aar:4f2b35916768032f7d0c20e250e28b29037ed4ce9ebf3da4fcd51bcb0c6067ef',
'androidx.activity:activity:1.2.0:activity-1.2.0.aar:ac27a810554e47b2122bce1f338934e77b173a5a9267eb35f134b6d34f931bae',
'androidx.annotation:annotation-experimental:1.0.0:annotation-experimental-1.0.0.aar:b219d2b568e7e4ba534e09f8c2fd242343df6ccbdfbbe938846f5d740e6b0b11',
'androidx.annotation:annotation:1.1.0:annotation-1.1.0.jar:d38d63edb30f1467818d50aaf05f8a692dea8b31392a049bfa991b159ad5b692',
'androidx.appcompat:appcompat-resources:1.2.0:appcompat-resources-1.2.0.aar:c470297c03ff3de1c3d15dacf0be0cae63abc10b52f021dd07ae28daa3100fe5',
@@ -24,29 +24,28 @@ dependencyVerification {
'androidx.exifinterface:exifinterface:1.3.1:exifinterface-1.3.1.aar:ef168daa6eb744c8395c22b49afa5235e6099868a0377175b6d5e3cdff8d7ffc',
'androidx.fragment:fragment-ktx:1.2.5:fragment-ktx-1.2.5.aar:50f0f3b734f93829eeac7456b7cb13e5430741e555c535911a958ee4a8242bca',
'androidx.fragment:fragment-testing:1.2.5:fragment-testing-1.2.5.aar:ef3cc3387115f9187665b283e313b13a2bb8826673380317057e2972351df09c',
'androidx.fragment:fragment:1.2.4:fragment-1.2.4.aar:1dc194942574302bf35dae7b81b82273505ec2d38f81d9258ad5c0448daddd82',
'androidx.fragment:fragment:1.2.5:fragment-1.2.5.aar:d19e82d142def6c4e136da70bf92f194c0ecc61d14ab4e84567b2ced0920fa93',
'androidx.fragment:fragment:1.3.0:fragment-1.3.0.aar:66db3ed2b11bb5e572a079b87cd3fae9bc5c33c373c71b25f1e3eac7607ab526',
'androidx.interpolator:interpolator:1.0.0:interpolator-1.0.0.aar:33193135a64fe21fa2c35eec6688f1a76e512606c0fc83dc1b689e37add7732a',
'androidx.legacy:legacy-support-core-utils:1.0.0:legacy-support-core-utils-1.0.0.aar:a7edcf01d5b52b3034073027bc4775b78a4764bb6202bb91d61c829add8dd1c7',
'androidx.lifecycle:lifecycle-common:2.2.0:lifecycle-common-2.2.0.jar:63898dabf7cfe5ec5d7ed8b8c2564c1427be876e1496ead95c2703cf59d3734b',
'androidx.lifecycle:lifecycle-common:2.3.0:lifecycle-common-2.3.0.jar:15848fb56db32f4c7cdc72b324003183d52a4884d6bf09be708ac7f587d139b5',
'androidx.lifecycle:lifecycle-extensions:2.2.0:lifecycle-extensions-2.2.0.aar:648c8de1d10b025d524a2e46ac994fc3f6bf186826c09ec1a62d250bf1b877ae',
'androidx.lifecycle:lifecycle-livedata-core-ktx:2.2.0:lifecycle-livedata-core-ktx-2.2.0.aar:5951f882e95b7e05ceb9adfca0fa2ebd511d63ea5a00da4eae6c6d0c1903da18',
'androidx.lifecycle:lifecycle-livedata-core:2.2.0:lifecycle-livedata-core-2.2.0.aar:556c1f3af90aa9d7d0d330565adbf6da71b2429148bac91e07c485f4f9abf614',
'androidx.lifecycle:lifecycle-livedata-core:2.3.0:lifecycle-livedata-core-2.3.0.aar:89f480888f2bb8eb62d9b7b1eb34be69b59ec84b24a1b0bdbeb49973478c6da3',
'androidx.lifecycle:lifecycle-livedata:2.2.0:lifecycle-livedata-2.2.0.aar:d83af94860aa9f64cbdc51f40796a7cf55b116f0e6efd752e845c0104c8b16f6',
'androidx.lifecycle:lifecycle-process:2.2.0:lifecycle-process-2.2.0.aar:3a977e7778fc8418742d388409daaba7ea8fea8823d21ffb96e4c4236f715070',
'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0:lifecycle-runtime-ktx-2.2.0.aar:c29fc87694e6ce116b61207221e53ed285862a6628055790b0bcf9ce45d8cc68',
'androidx.lifecycle:lifecycle-runtime:2.2.0:lifecycle-runtime-2.2.0.aar:2f866c07a1f33a8c9bb69a9545d4f20b4f0628cd0a155432386d7cb081e1e0bc',
'androidx.lifecycle:lifecycle-runtime:2.3.0:lifecycle-runtime-2.3.0.aar:94f528fd5fb123f75b6e65d07a6ef5cd6c0e69ac604d106aaa12705282456234',
'androidx.lifecycle:lifecycle-service:2.2.0:lifecycle-service-2.2.0.aar:ca2801ffc069555afed8eddd2292130f436956452bc8bbad30fb56f8e4e382a0',
'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0:lifecycle-viewmodel-ktx-2.2.0.aar:f791001f2211947e56ad3d96d12c9ae93fc5589b88f08603f69a2265c9a7d702',
'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0:lifecycle-viewmodel-savedstate-2.2.0.aar:3ce866fb822b20fe2f188f974992869a0a6233fe40acbefcff090d6def5e7f33',
'androidx.lifecycle:lifecycle-viewmodel:2.2.0:lifecycle-viewmodel-2.2.0.aar:967efab24d6c49dd414a8c0ac4a1cd09b018f0b8bb43b739ad360c4158ebde27',
'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0:lifecycle-viewmodel-savedstate-2.3.0.aar:49f9532b5104cc1ee64900ed4f696d031d807fba726e0d5d6a52459e8fba4a1d',
'androidx.lifecycle:lifecycle-viewmodel:2.3.0:lifecycle-viewmodel-2.3.0.aar:cea8f26fa232037922b69af9cd1bde2df1211acc8b75253e425b7150a5fca59d',
'androidx.loader:loader:1.0.0:loader-1.0.0.aar:11f735cb3b55c458d470bed9e25254375b518b4b1bad6926783a7026db0f5025',
'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0:localbroadcastmanager-1.0.0.aar:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8',
'androidx.preference:preference:1.1.1:preference-1.1.1.aar:317dcbc38242aea2f6262c06d51b8a22827e98959967edd40f82600a15cb4bff',
'androidx.print:print:1.0.0:print-1.0.0.aar:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd',
'androidx.recyclerview:recyclerview-selection:1.1.0-rc03:recyclerview-selection-1.1.0-rc03.aar:a548a0771c2c8ca8cf98f1f755b0eef4fac73d1697e6eeb1a6383f557e0eba13',
'androidx.recyclerview:recyclerview:1.1.0:recyclerview-1.1.0.aar:f0d2b5a67d0a91ee1b1c73ef2b636a81f3563925ddd15a1d4e1c41ec28de7a4f',
'androidx.savedstate:savedstate:1.0.0:savedstate-1.0.0.aar:2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83',
'androidx.savedstate:savedstate:1.1.0:savedstate-1.1.0.aar:d60bbe44c2c08083a17c5dc678a6d6b4d0a2d664858016ab5c049cbea90a63b7',
'androidx.test.espresso:espresso-contrib:3.3.0:espresso-contrib-3.3.0.aar:f400cabdc181356acf6b210e4509dcb9649d9e2b6b6e218c60fcfc15e8a756d1',
'androidx.test.espresso:espresso-core:3.3.0:espresso-core-3.3.0.aar:23ebf6014645e0c60aec7d1ed924d4d4c848ae8c3673b7d8d06b2ec6a56cafee',
'androidx.test.espresso:espresso-idling-resource:3.3.0:espresso-idling-resource-3.3.0.aar:29519b112731f289cc6e2f9b2eccc5ea72c754b04272bb93370f45d7e170a7c6',
@@ -57,6 +56,7 @@ dependencyVerification {
'androidx.test:monitor:1.3.0:monitor-1.3.0.aar:f73a31306a783e63150c60c49e140dc38da39a1b7947690f4b73387b5ebad77e',
'androidx.test:rules:1.3.0:rules-1.3.0.aar:c1753946c498b0d5d7cf341cfed661f66915c4c9deb4ed10462a08ae33b2429a',
'androidx.test:runner:1.3.0:runner-1.3.0.aar:61d13f5a9fcbbd73ba18fa84e1d6a0111c6e1c665a89b418126966e61fffd93b',
'androidx.tracing:tracing:1.0.0:tracing-1.0.0.aar:07b8b6139665b884a162eccf97891ca50f7f56831233bf25168ae04f7b568612',
'androidx.transition:transition:1.2.0:transition-1.2.0.aar:a1e059b3bc0b43a58dec0efecdcaa89c82d2bca552ea5bacf6656c46e853157e',
'androidx.vectordrawable:vectordrawable-animated:1.1.0:vectordrawable-animated-1.1.0.aar:76da2c502371d9c38054df5e2b248d00da87809ed058f3363eae87ce5e2403f8',
'androidx.vectordrawable:vectordrawable:1.1.0:vectordrawable-1.1.0.aar:46fd633ac01b49b7fcabc263bf098c5a8b9e9a69774d234edcca04fb02df8e26',

View File

@@ -105,7 +105,7 @@ The link and the alias should be posted as a JSON object:
}
```
This starts the process of adding the contact.
Adding a pending contact starts the process of adding the contact.
Until it is completed, a pending contact is returned as JSON:
```json
@@ -116,6 +116,71 @@ Until it is completed, a pending contact is returned as JSON:
}
```
Possible errors when adding a pending contact are:
#### 400: Pending contact's link is invalid
```json
{
"error": "INVALID_LINK"
}
```
#### 400: Pending contact's handshake public key is invalid
```json
{
"error": "INVALID_PUBLIC_KEY"
}
```
#### 403: A contact with the same handshake public key already exists
This error may be caused by someone attacking the user with the goal
of discovering the contacts of the user.
In the Android client, upon encountering this issue a message dialog
is shown that asks whether the contact and the just added pending contact
are the same person. If that's the case, a message is shown that the
contact already exists and the pending contact isn't added.
If that's not the case and they are two different persons, the Android
client
[shows the following message](https://code.briarproject.org/briar/briar/-/blob/beta-1.2.14/briar-android/src/main/res/values/strings.xml#L271)
when this happens:
> [Alice] and [Bob] sent you the same link.
>
> One of them may be trying to discover who your contacts are.
>
> Don't tell them you received the same link from someone else.
```json
{
"error": "CONTACT_EXISTS",
"remoteAuthorName": "Bob"
}
```
#### 403: A pending contact with the same handshake public key already exists
This error, too, may be caused by someone attacking the user with the goal
of discovering the contacts of the user.
Just like above, upon encountering this issue a message dialog is shown in
the Android client that asks whether the contact and the just added pending
contact are the same person. If that's the case, the pending contact gets
updated. If that's not the case and they are two different persons, the
Android client shows the same message as above, warning the user about the
possible attack.
```json
{
"error": "PENDING_EXISTS",
"pendingContactId": "jsTgWcsEQ2g9rnomeK1g/hmO8M1Ix6ZIGWAjgBtlS9U=",
"pendingContactAlias": "Alice"
}
```
-----------
Before users can send messages to contacts, they become pending contacts.
In this state Briar still needs to do some work in the background (e.g.
spinning up a dedicated hidden service and letting the contact connect to it).

View File

@@ -3,6 +3,8 @@ package org.briarproject.briar.headless.contact
import com.fasterxml.jackson.databind.ObjectMapper
import io.javalin.http.BadRequestResponse
import io.javalin.http.Context
import io.javalin.http.ForbiddenResponse
import io.javalin.http.HttpResponseException
import io.javalin.http.NotFoundResponse
import org.briarproject.bramble.api.connection.ConnectionRegistry
import org.briarproject.bramble.api.contact.ContactManager
@@ -12,8 +14,10 @@ import org.briarproject.bramble.api.contact.event.ContactAddedEvent
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent
import org.briarproject.bramble.api.db.ContactExistsException
import org.briarproject.bramble.api.db.NoSuchContactException
import org.briarproject.bramble.api.db.NoSuchPendingContactException
import org.briarproject.bramble.api.db.PendingContactExistsException
import org.briarproject.bramble.api.event.Event
import org.briarproject.bramble.api.event.EventListener
import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH
@@ -25,8 +29,11 @@ import org.briarproject.briar.headless.event.WebSocketController
import org.briarproject.briar.headless.getContactIdFromPathParam
import org.briarproject.briar.headless.getFromJson
import org.briarproject.briar.headless.json.JsonDict
import org.eclipse.jetty.http.HttpStatus.BAD_REQUEST_400
import org.eclipse.jetty.http.HttpStatus.FORBIDDEN_403
import org.spongycastle.util.encoders.Base64
import org.spongycastle.util.encoders.DecoderException
import java.security.GeneralSecurityException
import javax.annotation.concurrent.Immutable
import javax.inject.Inject
import javax.inject.Singleton
@@ -91,9 +98,32 @@ constructor(
override fun addPendingContact(ctx: Context): Context {
val link = ctx.getFromJson(objectMapper, "link")
val alias = ctx.getFromJson(objectMapper, "alias")
if (!LINK_REGEX.matcher(link).find()) throw BadRequestResponse("Invalid Link")
if (!LINK_REGEX.matcher(link).find()) {
ctx.status(BAD_REQUEST_400)
val details = mapOf("error" to "INVALID_LINK")
return ctx.json(details)
}
checkAliasLength(alias)
val pendingContact = contactManager.addPendingContact(link, alias)
val pendingContact = try {
contactManager.addPendingContact(link, alias)
} catch (e: GeneralSecurityException) {
ctx.status(BAD_REQUEST_400)
val details = mapOf("error" to "INVALID_PUBLIC_KEY")
return ctx.json(details)
} catch (e: ContactExistsException) {
ctx.status(FORBIDDEN_403)
val details =
mapOf("error" to "CONTACT_EXISTS", "remoteAuthorName" to e.remoteAuthor.name)
return ctx.json(details)
} catch (e: PendingContactExistsException) {
ctx.status(FORBIDDEN_403)
val details = mapOf(
"error" to "PENDING_EXISTS",
"pendingContactId" to e.pendingContact.id.bytes,
"pendingContactAlias" to e.pendingContact.alias
)
return ctx.json(details)
}
return ctx.json(pendingContact.output())
}

View File

@@ -98,6 +98,49 @@ class ContactControllerIntegrationTest: IntegrationTest() {
assertEquals(401, response.statusCode)
}
@Test
fun `adding a pending contact with invalid link`() {
val alias = "AliasFoo"
val json = """{
"link": "briar://invalid",
"alias": "$alias"
}"""
val response = post("$url/contacts/add/pending", json)
assertEquals(400, response.statusCode)
assertEquals("INVALID_LINK", response.jsonObject.getString("error"))
}
@Test
fun `adding a pending contact with invalid public key`() {
val alias = "AliasFoo"
val json = """{
"link": "briar://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"alias": "$alias"
}"""
val response = post("$url/contacts/add/pending", json)
assertEquals(400, response.statusCode)
assertEquals("INVALID_PUBLIC_KEY", response.jsonObject.getString("error"))
}
@Test
fun `adding a pending contact that already exists as pending contact`() {
val alias = "AliasFoo"
val json = """{
"link": "${getRealHandshakeLink(crypto)}",
"alias": "$alias"
}"""
var response = post("$url/contacts/add/pending", json)
assertEquals(200, response.statusCode)
val pendingContactId = response.jsonObject.getString("pendingContactId")
response = post("$url/contacts/add/pending", json)
assertEquals(403, response.statusCode)
assertEquals("PENDING_EXISTS", response.jsonObject.getString("error"))
assertEquals(pendingContactId, response.jsonObject.getString("pendingContactId"))
assertEquals(alias, response.jsonObject.getString("pendingContactAlias"))
}
@Test
fun `removing a pending contact needs authentication token`() {
val response = deleteWithWrongToken("$url/contacts/add/pending")

View File

@@ -1,6 +1,8 @@
package org.briarproject.briar.headless.contact
import io.javalin.http.BadRequestResponse
import io.javalin.http.ForbiddenResponse
import io.javalin.http.HttpResponseException
import io.javalin.http.NotFoundResponse
import io.javalin.plugin.json.JavalinJson.toJson
import io.mockk.Runs
@@ -8,6 +10,7 @@ import io.mockk.every
import io.mockk.just
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.verify
import org.briarproject.bramble.api.Pair
import org.briarproject.bramble.api.contact.Contact
import org.briarproject.bramble.api.contact.ContactId
@@ -18,8 +21,10 @@ import org.briarproject.bramble.api.contact.event.ContactAddedEvent
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent
import org.briarproject.bramble.api.db.ContactExistsException
import org.briarproject.bramble.api.db.NoSuchContactException
import org.briarproject.bramble.api.db.NoSuchPendingContactException
import org.briarproject.bramble.api.db.PendingContactExistsException
import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent
@@ -30,9 +35,11 @@ import org.briarproject.bramble.util.StringUtils.getRandomString
import org.briarproject.briar.headless.ControllerTest
import org.briarproject.briar.headless.getFromJson
import org.briarproject.briar.headless.json.JsonDict
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test
import java.security.GeneralSecurityException
import kotlin.random.Random
internal class ContactControllerTest : ControllerTest() {
@@ -96,9 +103,10 @@ internal class ContactControllerTest : ControllerTest() {
"alias": "$alias"
}"""
every { ctx.body() } returns body
assertThrows(BadRequestResponse::class.java) {
controller.addPendingContact(ctx)
}
every { ctx.status(400) } returns ctx
every { ctx.json(mapOf("error" to "INVALID_LINK")) } returns ctx
controller.addPendingContact(ctx)
verify { ctx.status(400) }
}
@Test
@@ -139,6 +147,84 @@ internal class ContactControllerTest : ControllerTest() {
}
}
@Test
fun testAddPendingContactPublicKeyInvalid() {
val link = "briar://adnsyffpsenoc3yzlhr24aegfq2pwan7kkselocill2choov6sbhs"
val alias = "Alias123"
val body = """{
"link": "$link",
"alias": "$alias"
}"""
every { ctx.body() } returns body
every { ctx.status(400) } returns ctx
every {
contactManager.addPendingContact(
link,
alias
)
} throws GeneralSecurityException()
every { ctx.json(mapOf("error" to "INVALID_PUBLIC_KEY")) } returns ctx
controller.addPendingContact(ctx)
verify { ctx.status(400) }
}
@Test
fun testAddPendingContactSameContactKey() {
val link = "briar://adnsyffpsenoc3yzlhr24aegfq2pwan7kkselocill2choov6sbhs"
val alias = "Alias123"
val body = """{
"link": "$link",
"alias": "$alias"
}"""
every { ctx.body() } returns body
every { ctx.status(403) } returns ctx
every {
contactManager.addPendingContact(
link,
alias
)
} throws ContactExistsException(null, author)
every {
ctx.json(
mapOf(
"error" to "CONTACT_EXISTS",
"remoteAuthorName" to author.name
)
)
} returns ctx
controller.addPendingContact(ctx)
verify { ctx.status(403) }
}
@Test
fun testAddPendingContactSamePendingContactKey() {
val link = "briar://adnsyffpsenoc3yzlhr24aegfq2pwan7kkselocill2choov6sbhs"
val alias = "Alias123"
val body = """{
"link": "$link",
"alias": "$alias"
}"""
every { ctx.body() } returns body
every { ctx.status(403) } returns ctx
every {
contactManager.addPendingContact(
link,
alias
)
} throws PendingContactExistsException(pendingContact)
every {
ctx.json(
mapOf(
"error" to "PENDING_EXISTS",
"pendingContactId" to pendingContact.id.bytes,
"pendingContactAlias" to pendingContact.alias
)
)
} returns ctx
controller.addPendingContact(ctx)
verify { ctx.status(403) }
}
@Test
fun testListPendingContacts() {
every { contactManager.pendingContacts } returns listOf(