Browse Source

Add new Auth workflow.

pull/16/head
Mushegh Sahakyan 4 years ago
parent
commit
c815ece00c
7 changed files with 412 additions and 42 deletions
  1. +54
    -41
      ui/src/main/java/com/getbubblenow/android/activity/LoginActivity.java
  2. +4
    -0
      ui/src/main/java/com/getbubblenow/android/api/ApiConstants.java
  3. +8
    -0
      ui/src/main/java/com/getbubblenow/android/api/network/ClientApi.java
  4. +228
    -0
      ui/src/main/java/com/getbubblenow/android/model/Network.java
  5. +21
    -0
      ui/src/main/java/com/getbubblenow/android/model/Sages.java
  6. +85
    -1
      ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java
  7. +12
    -0
      ui/src/main/java/com/getbubblenow/android/viewmodel/LoginViewModel.java

+ 54
- 41
ui/src/main/java/com/getbubblenow/android/activity/LoginActivity.java View File

@@ -3,6 +3,7 @@ package com.getbubblenow.android.activity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatButton;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

@@ -18,6 +19,7 @@ import android.widget.EditText;
import android.widget.Toast;

import com.getbubblenow.android.R;
import com.getbubblenow.android.api.ApiConstants;
import com.getbubblenow.android.repository.DataRepository;
import com.getbubblenow.android.resource.StatusResource;
import com.getbubblenow.android.viewmodel.LoginViewModel;
@@ -25,7 +27,6 @@ import com.getbubblenow.android.viewmodel.LoginViewModel;
public class LoginActivity extends BaseActivityBubble {

private LoginViewModel loginViewModel;
private EditText bubbleName;
private EditText userName;
private EditText password;
private AppCompatButton sign;
@@ -35,14 +36,13 @@ public class LoginActivity extends BaseActivityBubble {
private static final String SEPARATOR = "\\.";
private static final int REQUEST_CODE = 1555;
private static final String CERTIFICATE_NAME = "Bubble Certificate";
private boolean bubbleNameStateFlag = false;
private boolean userNameStateFlag = false;
private boolean passwordStateFlag = false;
private static final String BUBBLE_NAME_KEY = "bubbleName";
private static final String USER_NAME_KEY = "userName";
private static final String PASSWORD_KEY = "password";
private static final String NO_INTERNET_CONNECTION = "no internet connection";
private static final String LOGIN_FAILED = "Login Failed";
private static String bubbleName = "";


@Override
@@ -61,58 +61,73 @@ public class LoginActivity extends BaseActivityBubble {
private void initListeners() {
sign.setOnClickListener(new OnClickListener() {
@Override public void onClick(final View v) {
final String url = BASE_URL_PREFIX + bubbleName.getText().toString() + BASE_URL_SUFFIX;
if (url.split(SEPARATOR).length != 3) {
Toast.makeText(LoginActivity.this, getResources().getText(R.string.hostname_not_valid), Toast.LENGTH_LONG).show();
return;
}

if (DataRepository.getRepositoryInstance() == null) {
loginViewModel.buildRepositoryInstance(LoginActivity.this, url);
loginViewModel.buildRepositoryInstance(LoginActivity.this, ApiConstants.BOOTSTRAP_URL);
} else {
loginViewModel.buildClientService(url);
loginViewModel.buildClientService(ApiConstants.BOOTSTRAP_URL);
}
loginViewModel.setUserURL(LoginActivity.this, url);
final String usernameInput = userName.getText().toString().trim();
final String passwordInput = password.getText().toString().trim();
showLoadingDialog();
login(usernameInput, passwordInput);
loginViewModel.getNodeLiveData().observe(LoginActivity.this, new Observer<StatusResource<String>>() {
@Override public void onChanged(final StatusResource<String> stringStatusResource) {
switch (stringStatusResource.status) {
case SUCCESS:
final String url = stringStatusResource.data;
loginViewModel.buildClientService(BASE_URL_PREFIX + url + BASE_URL_SUFFIX);
loginViewModel.setUserURL(LoginActivity.this, BASE_URL_PREFIX + url + BASE_URL_SUFFIX);
bubbleName = url;
loginViewModel.setNodeLiveData(new MutableLiveData<>());
login(usernameInput, passwordInput);
break;
case ERROR:
closeLoadingDialog();
if (stringStatusResource.message.equals(NO_INTERNET_CONNECTION)) {
showNetworkNotAvailableMessage();
}
else if (stringStatusResource.message.equals(LOGIN_FAILED)) {
Toast.makeText(LoginActivity.this, LOGIN_FAILED, Toast.LENGTH_LONG).show();
}
else {
showErrorDialog(stringStatusResource.message);
}
break;
case LOADING:
break;
}
}
});
loginViewModel.getSages(LoginActivity.this,usernameInput,passwordInput).observe(LoginActivity.this, new Observer<StatusResource<String>>() {
@Override public void onChanged(final StatusResource<String> stringStatusResource) {
switch (stringStatusResource.status){
case ERROR:
closeLoadingDialog();
if(stringStatusResource.message.equals(NO_INTERNET_CONNECTION)){
showNetworkNotAvailableMessage();
}
else if(stringStatusResource.message.equals(LOGIN_FAILED)){
Toast.makeText(LoginActivity.this,LOGIN_FAILED,Toast.LENGTH_LONG).show();
}
else {
showErrorDialog(stringStatusResource.message);
}
break;
}
}
});
}
});
bubbleNameStateListener();
userNameStateListener();
passwordStateListener();
}


private void initViews() {
bubbleName = findViewById(R.id.bubbleName);
userName = findViewById(R.id.userName);
password = findViewById(R.id.password);
sign = findViewById(R.id.signButton);
}

private void bubbleNameStateListener() {
bubbleName.addTextChangedListener(new TextWatcher() {
@Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {

}

@Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {

}

@Override public void afterTextChanged(final Editable s) {

if (bubbleName.getText().toString().trim().isEmpty()) {
bubbleNameStateFlag = false;
} else {
bubbleNameStateFlag = true;
}
setButtonState();
}
});
}

private void userNameStateListener() {
userName.addTextChangedListener(new TextWatcher() {
@Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
@@ -159,7 +174,7 @@ public class LoginActivity extends BaseActivityBubble {
}

private void setButtonState(){
if(userNameStateFlag && bubbleNameStateFlag && passwordStateFlag){
if(userNameStateFlag && passwordStateFlag){
sign.setBackgroundDrawable(getDrawable(R.drawable.sign_in_enable));
sign.setEnabled(true);
}
@@ -210,7 +225,7 @@ public class LoginActivity extends BaseActivityBubble {
switch (objectStatusResource.status){
case SUCCESS:
Toast.makeText(LoginActivity.this, getString(R.string.success), Toast.LENGTH_SHORT).show();
loginViewModel.setHostName(LoginActivity.this,bubbleName.getText().toString().trim());
loginViewModel.setHostName(LoginActivity.this,bubbleName);
Log.d("TAG", "Success");
final Intent mainActivityIntent = new Intent(LoginActivity.this, MainActivity.class);
mainActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -238,14 +253,12 @@ public class LoginActivity extends BaseActivityBubble {
}

@Override protected void onSaveInstanceState(@NonNull final Bundle outState) {
outState.putBoolean(BUBBLE_NAME_KEY,bubbleNameStateFlag);
outState.putBoolean(USER_NAME_KEY,userNameStateFlag);
outState.putBoolean(PASSWORD_KEY,passwordStateFlag);
super.onSaveInstanceState(outState);
}

@Override protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
bubbleNameStateFlag = savedInstanceState.getBoolean(BUBBLE_NAME_KEY);
userNameStateFlag = savedInstanceState.getBoolean(USER_NAME_KEY);
passwordStateFlag = savedInstanceState.getBoolean(PASSWORD_KEY);
super.onRestoreInstanceState(savedInstanceState);


+ 4
- 0
ui/src/main/java/com/getbubblenow/android/api/ApiConstants.java View File

@@ -1,6 +1,10 @@
package com.getbubblenow.android.api;

public class ApiConstants {
public static final String BOOTSTRAP_URL = "https://raw.githubusercontent.com/getbubblenow/";
public static final String BOOTSTRAP_URL_SUFFIX = "bubble-config/master/boot.json";
public static String BASE_URL = "";
public static final String NODE_BASE_URI = "me/networks";
public static final String LOGIN_URL = "auth/login";
public static final String ALL_DEVICES_URL = "me/devices";
public static final String ADD_DEVICE_URL = "me/devices";


+ 8
- 0
ui/src/main/java/com/getbubblenow/android/api/network/ClientApi.java View File

@@ -1,6 +1,8 @@
package com.getbubblenow.android.api.network;

import com.getbubblenow.android.model.Device;
import com.getbubblenow.android.model.Network;
import com.getbubblenow.android.model.Sages;
import com.getbubblenow.android.model.User;
import com.getbubblenow.android.api.ApiConstants;
import com.getbubblenow.android.model.Device;
@@ -39,4 +41,10 @@ public interface ClientApi {

@GET(ApiConstants.CONFIG_DEVICE_URL+"{deviceID}"+ApiConstants.CONFIG_VPN_URL)
Single<ResponseBody> getConfig(@Path("deviceID") String deviceID , @HeaderMap HashMap<String,String> header);

@GET(ApiConstants.BOOTSTRAP_URL_SUFFIX)
Single<Sages> getSages();

@GET(ApiConstants.NODE_BASE_URI)
Single<List<Network>> getNodeBaseURI(@HeaderMap HashMap<String,String> header);
}

+ 228
- 0
ui/src/main/java/com/getbubblenow/android/model/Network.java View File

@@ -0,0 +1,228 @@
package com.getbubblenow.android.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Network {

@SerializedName("uuid")
@Expose
private String uuid;
@SerializedName("related")
@Expose
private String related;
@SerializedName("name")
@Expose
private String name;
@SerializedName("account")
@Expose
private String account;
@SerializedName("domain")
@Expose
private String domain;
@SerializedName("domainName")
@Expose
private String domainName;
@SerializedName("sslPort")
@Expose
private long sslPort;
@SerializedName("installType")
@Expose
private String installType;
@SerializedName("sshKey")
@Expose
private String sshKey;
@SerializedName("computeSizeType")
@Expose
private String computeSizeType;
@SerializedName("footprint")
@Expose
private String footprint;
@SerializedName("storage")
@Expose
private String storage;
@SerializedName("description")
@Expose
private String description;
@SerializedName("locale")
@Expose
private String locale;
@SerializedName("timezone")
@Expose
private String timezone;
@SerializedName("sendMetrics")
@Expose
private boolean sendMetrics;
@SerializedName("tags")
@Expose
private String tags;
@SerializedName("forkHost")
@Expose
private String forkHost;
@SerializedName("state")
@Expose
private String state;
@SerializedName("shortId")
@Expose
private String shortId;

public String getUuid() {
return uuid;
}

public void setUuid(final String uuid) {
this.uuid = uuid;
}

public String getRelated() {
return related;
}

public void setRelated(final String related) {
this.related = related;
}

public String getName() {
return name;
}

public void setName(final String name) {
this.name = name;
}

public String getAccount() {
return account;
}

public void setAccount(final String account) {
this.account = account;
}

public String getDomain() {
return domain;
}

public void setDomain(final String domain) {
this.domain = domain;
}

public String getDomainName() {
return domainName;
}

public void setDomainName(final String domainName) {
this.domainName = domainName;
}

public long getSslPort() {
return sslPort;
}

public void setSslPort(final long sslPort) {
this.sslPort = sslPort;
}

public String getInstallType() {
return installType;
}

public void setInstallType(final String installType) {
this.installType = installType;
}

public String getSshKey() {
return sshKey;
}

public void setSshKey(final String sshKey) {
this.sshKey = sshKey;
}

public String getComputeSizeType() {
return computeSizeType;
}

public void setComputeSizeType(final String computeSizeType) {
this.computeSizeType = computeSizeType;
}

public String getFootprint() {
return footprint;
}

public void setFootprint(final String footprint) {
this.footprint = footprint;
}

public String getStorage() {
return storage;
}

public void setStorage(final String storage) {
this.storage = storage;
}

public String getDescription() {
return description;
}

public void setDescription(final String description) {
this.description = description;
}

public String getLocale() {
return locale;
}

public void setLocale(final String locale) {
this.locale = locale;
}

public String getTimezone() {
return timezone;
}

public void setTimezone(final String timezone) {
this.timezone = timezone;
}

public boolean isSendMetrics() {
return sendMetrics;
}

public void setSendMetrics(final boolean sendMetrics) {
this.sendMetrics = sendMetrics;
}

public String getTags() {
return tags;
}

public void setTags(final String tags) {
this.tags = tags;
}

public String getForkHost() {
return forkHost;
}

public void setForkHost(final String forkHost) {
this.forkHost = forkHost;
}

public String getState() {
return state;
}

public void setState(final String state) {
this.state = state;
}

public String getShortId() {
return shortId;
}

public void setShortId(final String shortId) {
this.shortId = shortId;
}
}

+ 21
- 0
ui/src/main/java/com/getbubblenow/android/model/Sages.java View File

@@ -0,0 +1,21 @@
package com.getbubblenow.android.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

import java.util.List;

public class Sages {

@SerializedName("sages")
@Expose
List<String> sages;

public void setSages(final List<String> sages) {
this.sages = sages;
}

public List<String> getSages() {
return sages;
}
}

+ 85
- 1
ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java View File

@@ -5,11 +5,13 @@ import android.content.Intent;
import android.os.Build;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.util.Log;
import android.widget.Toast;

import com.getbubblenow.android.Application;
import com.getbubblenow.android.R;
import com.getbubblenow.android.configStore.FileConfigStore;
import com.getbubblenow.android.model.Network;
import com.getbubblenow.android.model.ObservableTunnel;
import com.getbubblenow.android.model.TunnelManager;
import com.getbubblenow.android.activity.MainActivity;
@@ -47,7 +49,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okio.Buffer;
import retrofit2.Call;
@@ -68,9 +69,22 @@ public class DataRepository {
private static final int REQUEST_CODE_VPN_PERMISSION = 23491;
private static final String NO_INTERNET_CONNECTION = "no internet connection";
private static final String LOGIN_FAILED = "Login Failed";
private static final String URL_SUFFIX = "/api/";
private static final String RUNNING = "running";
private static String token = "";
private static String deviceName;
private static String deviceID;
private MutableLiveData<StatusResource<String>> nodeLiveData = new MutableLiveData<>();
private List<String> nodes;
private int nodeIndex = 0;

public MutableLiveData<StatusResource<String>> getNodeLiveData() {
return nodeLiveData;
}

public void setNodeLiveData(final MutableLiveData<StatusResource<String>> nodeLiveData) {
this.nodeLiveData = nodeLiveData;
}

private DataRepository(Context context, String url) {
BASE_URL = url;
@@ -98,6 +112,76 @@ public class DataRepository {
return instance;
}

private void getNodeIndex(int index, String username , String password, Context context){
if(index<nodes.size())
{
getNetwork(nodes.get(index),username,password,context);
}
else {
nodeLiveData.postValue(StatusResource.error(LOGIN_FAILED));
}
}

private void getNetwork(String node, String username , String password, Context context){

HashMap<String, String> data = new HashMap<>();
data.put(ApiConstants.USERNAME, username);
data.put(ApiConstants.PASSWORD, password);
buildClientService(node + URL_SUFFIX);
Disposable disposableLogin = clientApi.login(data)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> {
token = user.getToken();
ApiConstants.BASE_URL = node + URL_SUFFIX;
buildClientService(ApiConstants.BASE_URL);
final HashMap<String, String> header = new HashMap<>();
header.put(ApiConstants.AUTHORIZATION_HEADER, token);
Disposable getNodeBaseURIDisposable = clientApi.getNodeBaseURI(header)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(networks -> {
for (Network network : networks) {
if (network.getState().equals(RUNNING)) {
ApiConstants.BASE_URL = network.getName() + "." + network.getDomainName();
BASE_URL = ApiConstants.BASE_URL;
nodeLiveData.postValue(StatusResource.success(BASE_URL));
break;
}
}
}, throwable -> {
Log.d("ERR", "getNodeBaseURI");
getNodeIndex(nodeIndex++,username,password,context);
});
compositeDisposable.add(getNodeBaseURIDisposable);
}, throwable -> {
getNodeIndex(nodeIndex++,username,password,context);
Log.d("ERR", "getSages-login");
});
compositeDisposable.add(disposableLogin);

}

public MutableLiveData<StatusResource<String>> getSages(Context context, String username, String password){
return new NetworkBoundStatusResource<String>(){

@Override protected void createCall() {
Disposable sagesDisposable = clientApi.getSages()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(sages -> {
nodes = sages.getSages();
getNodeIndex(nodeIndex,username,password,context);
},throwable -> {
Log.d("ERR","getSages");
setErrorMessage(throwable,this);
});
compositeDisposable.add(sagesDisposable);
}
}.getMutableLiveData();
}


public MutableLiveData<StatusResource<byte[]>> login(String username, String password, Context context) {
return new NetworkBoundStatusResource<byte[]>() {
@Override protected void createCall() {


+ 12
- 0
ui/src/main/java/com/getbubblenow/android/viewmodel/LoginViewModel.java View File

@@ -38,4 +38,16 @@ public class LoginViewModel extends ViewModel {
public MutableLiveData<StatusResource<Object>> createTunnel(Context context) {
return DataRepository.getRepositoryInstance().createTunnel(context);
}

public MutableLiveData<StatusResource<String>> getNodeLiveData() {
return DataRepository.getRepositoryInstance().getNodeLiveData();
}

public void setNodeLiveData(final MutableLiveData<StatusResource<String>> nodeLiveData) {
DataRepository.getRepositoryInstance().setNodeLiveData(nodeLiveData);
}

public MutableLiveData<StatusResource<String>> getSages(Context context, String username, String password){
return DataRepository.getRepositoryInstance().getSages(context, username, password);
}
}

Loading…
Cancel
Save