From c815ece00c8c3ea42d8007f829d2d26ac9512069 Mon Sep 17 00:00:00 2001 From: Mushegh98 Date: Fri, 19 Jun 2020 17:15:52 +0400 Subject: [PATCH] Add new Auth workflow. --- .../android/activity/LoginActivity.java | 95 ++++---- .../android/api/ApiConstants.java | 4 + .../android/api/network/ClientApi.java | 8 + .../getbubblenow/android/model/Network.java | 228 ++++++++++++++++++ .../com/getbubblenow/android/model/Sages.java | 21 ++ .../android/repository/DataRepository.java | 86 ++++++- .../android/viewmodel/LoginViewModel.java | 12 + 7 files changed, 412 insertions(+), 42 deletions(-) create mode 100644 ui/src/main/java/com/getbubblenow/android/model/Network.java create mode 100644 ui/src/main/java/com/getbubblenow/android/model/Sages.java diff --git a/ui/src/main/java/com/getbubblenow/android/activity/LoginActivity.java b/ui/src/main/java/com/getbubblenow/android/activity/LoginActivity.java index 0ca09a9..f4af74f 100644 --- a/ui/src/main/java/com/getbubblenow/android/activity/LoginActivity.java +++ b/ui/src/main/java/com/getbubblenow/android/activity/LoginActivity.java @@ -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>() { + @Override public void onChanged(final StatusResource 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>() { + @Override public void onChanged(final StatusResource 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); diff --git a/ui/src/main/java/com/getbubblenow/android/api/ApiConstants.java b/ui/src/main/java/com/getbubblenow/android/api/ApiConstants.java index 5f46f3f..293e4bd 100644 --- a/ui/src/main/java/com/getbubblenow/android/api/ApiConstants.java +++ b/ui/src/main/java/com/getbubblenow/android/api/ApiConstants.java @@ -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"; diff --git a/ui/src/main/java/com/getbubblenow/android/api/network/ClientApi.java b/ui/src/main/java/com/getbubblenow/android/api/network/ClientApi.java index f5c9ac2..d9f4fd9 100644 --- a/ui/src/main/java/com/getbubblenow/android/api/network/ClientApi.java +++ b/ui/src/main/java/com/getbubblenow/android/api/network/ClientApi.java @@ -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 getConfig(@Path("deviceID") String deviceID , @HeaderMap HashMap header); + + @GET(ApiConstants.BOOTSTRAP_URL_SUFFIX) + Single getSages(); + + @GET(ApiConstants.NODE_BASE_URI) + Single> getNodeBaseURI(@HeaderMap HashMap header); } diff --git a/ui/src/main/java/com/getbubblenow/android/model/Network.java b/ui/src/main/java/com/getbubblenow/android/model/Network.java new file mode 100644 index 0000000..a8d532a --- /dev/null +++ b/ui/src/main/java/com/getbubblenow/android/model/Network.java @@ -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; + } +} diff --git a/ui/src/main/java/com/getbubblenow/android/model/Sages.java b/ui/src/main/java/com/getbubblenow/android/model/Sages.java new file mode 100644 index 0000000..7a22dd7 --- /dev/null +++ b/ui/src/main/java/com/getbubblenow/android/model/Sages.java @@ -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 sages; + + public void setSages(final List sages) { + this.sages = sages; + } + + public List getSages() { + return sages; + } +} diff --git a/ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java b/ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java index 2e1e009..5452804 100644 --- a/ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java +++ b/ui/src/main/java/com/getbubblenow/android/repository/DataRepository.java @@ -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> nodeLiveData = new MutableLiveData<>(); + private List nodes; + private int nodeIndex = 0; + + public MutableLiveData> getNodeLiveData() { + return nodeLiveData; + } + + public void setNodeLiveData(final MutableLiveData> 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 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 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> getSages(Context context, String username, String password){ + return new NetworkBoundStatusResource(){ + + @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> login(String username, String password, Context context) { return new NetworkBoundStatusResource() { @Override protected void createCall() { diff --git a/ui/src/main/java/com/getbubblenow/android/viewmodel/LoginViewModel.java b/ui/src/main/java/com/getbubblenow/android/viewmodel/LoginViewModel.java index 031d0ce..4e1e7bf 100644 --- a/ui/src/main/java/com/getbubblenow/android/viewmodel/LoginViewModel.java +++ b/ui/src/main/java/com/getbubblenow/android/viewmodel/LoginViewModel.java @@ -38,4 +38,16 @@ public class LoginViewModel extends ViewModel { public MutableLiveData> createTunnel(Context context) { return DataRepository.getRepositoryInstance().createTunnel(context); } + + public MutableLiveData> getNodeLiveData() { + return DataRepository.getRepositoryInstance().getNodeLiveData(); + } + + public void setNodeLiveData(final MutableLiveData> nodeLiveData) { + DataRepository.getRepositoryInstance().setNodeLiveData(nodeLiveData); + } + + public MutableLiveData> getSages(Context context, String username, String password){ + return DataRepository.getRepositoryInstance().getSages(context, username, password); + } }