Merge branch 'kvvservice'
This commit is contained in:
@@ -2,28 +2,42 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="de.sebse.fuplanner">
|
package="de.sebse.fuplanner">
|
||||||
|
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.AUTHENTICATE_ACCOUNTS"
|
||||||
|
android:maxSdkVersion="22" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.MANAGE_ACCOUNTS"
|
||||||
|
android:maxSdkVersion="22" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.USE_CREDENTIALS"
|
||||||
|
android:maxSdkVersion="22" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<!-- To auto-complete the email text field in the login form with the user's emails -->
|
||||||
|
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||||
|
<uses-permission android:name="android.permission.READ_PROFILE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
android:fullBackupContent="@xml/backup_descriptor"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/FUTheme"
|
android:theme="@style/FUTheme">
|
||||||
android:fullBackupContent="@xml/backup_descriptor">
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.my.provider"
|
android:authorities="${applicationId}.my.provider"
|
||||||
@@ -33,6 +47,18 @@
|
|||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/provider_paths" />
|
android:resource="@xml/provider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".services.newkvv.FUAuthenticatorActivity"
|
||||||
|
android:label="@string/title_activity_fuauthenticator" />
|
||||||
|
<service android:name=".services.newkvv.FUAuthenticatorService">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.accounts.AccountAuthenticator" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data android:name="android.accounts.AccountAuthenticator"
|
||||||
|
android:resource="@xml/authenticator" />
|
||||||
|
</service>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.sebse.fuplanner;
|
package de.sebse.fuplanner;
|
||||||
|
|
||||||
|
import android.accounts.AccountManager;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -28,7 +29,6 @@ import androidx.fragment.app.Fragment;
|
|||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
import de.sebse.fuplanner.fragments.CanteensFragment;
|
import de.sebse.fuplanner.fragments.CanteensFragment;
|
||||||
import de.sebse.fuplanner.fragments.LoginFragment;
|
|
||||||
import de.sebse.fuplanner.fragments.ModulesFragment;
|
import de.sebse.fuplanner.fragments.ModulesFragment;
|
||||||
import de.sebse.fuplanner.fragments.NewsFragment;
|
import de.sebse.fuplanner.fragments.NewsFragment;
|
||||||
import de.sebse.fuplanner.fragments.PrefsFragment;
|
import de.sebse.fuplanner.fragments.PrefsFragment;
|
||||||
@@ -46,6 +46,8 @@ import de.sebse.fuplanner.services.KVV.KVVListener;
|
|||||||
import de.sebse.fuplanner.services.KVV.types.LoginToken;
|
import de.sebse.fuplanner.services.KVV.types.LoginToken;
|
||||||
import de.sebse.fuplanner.services.KVV.types.Modules;
|
import de.sebse.fuplanner.services.KVV.types.Modules;
|
||||||
import de.sebse.fuplanner.services.News.NewsManager;
|
import de.sebse.fuplanner.services.News.NewsManager;
|
||||||
|
import de.sebse.fuplanner.services.newkvv.AccountGeneral;
|
||||||
|
import de.sebse.fuplanner.tools.CustomAccountManager;
|
||||||
import de.sebse.fuplanner.tools.MainActivityListener;
|
import de.sebse.fuplanner.tools.MainActivityListener;
|
||||||
import de.sebse.fuplanner.tools.NewAsyncQueue;
|
import de.sebse.fuplanner.tools.NewAsyncQueue;
|
||||||
import de.sebse.fuplanner.tools.Preferences;
|
import de.sebse.fuplanner.tools.Preferences;
|
||||||
@@ -67,7 +69,6 @@ public class MainActivity extends AppCompatActivity
|
|||||||
private static final int FRAGMENT_STARTUP = 0;
|
private static final int FRAGMENT_STARTUP = 0;
|
||||||
private static final int FRAGMENT_MODULES = 1;
|
private static final int FRAGMENT_MODULES = 1;
|
||||||
private static final int FRAGMENT_MODULES_DETAILS = 2;
|
private static final int FRAGMENT_MODULES_DETAILS = 2;
|
||||||
private static final int FRAGMENT_LOGIN = 3;
|
|
||||||
private static final int FRAGMENT_SCHEDULE = 4;
|
private static final int FRAGMENT_SCHEDULE = 4;
|
||||||
private static final int FRAGMENT_CANTEENS = 5;
|
private static final int FRAGMENT_CANTEENS = 5;
|
||||||
private static final int FRAGMENT_CANTEENS_DETAILS = 6;
|
private static final int FRAGMENT_CANTEENS_DETAILS = 6;
|
||||||
@@ -93,10 +94,14 @@ public class MainActivity extends AppCompatActivity
|
|||||||
private boolean mOfflineBanner;
|
private boolean mOfflineBanner;
|
||||||
private final NewAsyncQueue mQueue = new NewAsyncQueue();
|
private final NewAsyncQueue mQueue = new NewAsyncQueue();
|
||||||
private long mDoubleBackToExitPressedOnce = 0;
|
private long mDoubleBackToExitPressedOnce = 0;
|
||||||
|
private CustomAccountManager mAccountManager;
|
||||||
|
private boolean isPaused = false;
|
||||||
|
private boolean isLoggedInBeforePause = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
mAccountManager = new CustomAccountManager(AccountManager.get(this), () -> MainActivity.this);
|
||||||
int desiredPage = getDefaultFragmentAfterLogin();
|
int desiredPage = getDefaultFragmentAfterLogin();
|
||||||
String desiredData = "";
|
String desiredData = "";
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
@@ -118,12 +123,53 @@ public class MainActivity extends AppCompatActivity
|
|||||||
mNavigationView.setNavigationItemSelectedListener(this);
|
mNavigationView.setNavigationItemSelectedListener(this);
|
||||||
mFragmentManager = getSupportFragmentManager();
|
mFragmentManager = getSupportFragmentManager();
|
||||||
|
|
||||||
if (!getKVV().account().restoreOnlineLogin()) {
|
//if (mAccountManager.getAccountsByType(AccountGeneral.ACCOUNT_TYPE).length == 0) {
|
||||||
desiredPage = FRAGMENT_LOGIN;
|
if (!mAccountManager.hasAccounts(AccountGeneral.ACCOUNT_TYPE)) {
|
||||||
|
desiredPage = getDefaultFragmentAfterLogout();
|
||||||
desiredData = "";
|
desiredData = "";
|
||||||
}
|
mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, null);
|
||||||
updateNavigation();
|
updateNavigation();
|
||||||
changeFragment(desiredPage, desiredData);
|
changeFragment(desiredPage, desiredData);
|
||||||
|
} else {
|
||||||
|
updateNavigation();
|
||||||
|
changeFragment(FRAGMENT_STARTUP);
|
||||||
|
int targetPage = desiredPage;
|
||||||
|
String targetData = desiredData;
|
||||||
|
getKVV().account().restoreOnlineLogin(isRestored -> {
|
||||||
|
updateNavigation();
|
||||||
|
if (isRestored)
|
||||||
|
changeFragment(targetPage, targetData);
|
||||||
|
else
|
||||||
|
changeFragment(getDefaultFragmentAfterLogout());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
isPaused = true;
|
||||||
|
isLoggedInBeforePause = getKVV().account().isLoggedIn();
|
||||||
|
log.d("onPause", isPaused, isLoggedInBeforePause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
log.d("onResume", isPaused, isLoggedInBeforePause);
|
||||||
|
if (isPaused) {
|
||||||
|
getKVV().account().restoreOnlineLogin(isRestored -> {
|
||||||
|
log.d("onResume", isRestored);
|
||||||
|
updateNavigation();
|
||||||
|
if (isRestored && !isLoggedInBeforePause)
|
||||||
|
changeFragment(getDefaultFragmentAfterLogin());
|
||||||
|
else if (!isRestored && isLoggedInBeforePause) {
|
||||||
|
getKVV().account().logout(false);
|
||||||
|
changeFragment(getDefaultFragmentAfterLogout());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
isPaused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -153,11 +199,10 @@ public class MainActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
} else if (getKVV().account().isLoggedIn() && mFragmentPage != getDefaultFragmentAfterLogin()) {
|
} else if (getKVV().account().isLoggedIn() && mFragmentPage != getDefaultFragmentAfterLogin()) {
|
||||||
changeFragment(getDefaultFragmentAfterLogin());
|
changeFragment(getDefaultFragmentAfterLogin());
|
||||||
} else if (!getKVV().account().isLoggedIn() && mFragmentPage != FRAGMENT_LOGIN) {
|
|
||||||
changeFragment(FRAGMENT_LOGIN);
|
|
||||||
} else {
|
} else {
|
||||||
mDoubleBackToExitPressedOnce = System.currentTimeMillis();
|
mDoubleBackToExitPressedOnce = System.currentTimeMillis();
|
||||||
showToast(R.string.back_to_exit);
|
showToast(R.string.back_to_exit);
|
||||||
|
//getTokenForAccountCreateIfNeeded(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,7 +300,7 @@ public class MainActivity extends AppCompatActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle savedInstanceState) {
|
protected void onSaveInstanceState(Bundle savedInstanceState) {
|
||||||
if (mFragmentPage != FRAGMENT_STARTUP && mFragmentPage != FRAGMENT_NONE && mFragmentPage != FRAGMENT_LOGIN) {
|
if (mFragmentPage != FRAGMENT_STARTUP && mFragmentPage != FRAGMENT_NONE) {
|
||||||
Fragment fragment = mFragmentManager.findFragmentByTag(String.valueOf(mFragmentPage));
|
Fragment fragment = mFragmentManager.findFragmentByTag(String.valueOf(mFragmentPage));
|
||||||
savedInstanceState.putInt(ARG_FRAGMENT_PAGE, mFragmentPage);
|
savedInstanceState.putInt(ARG_FRAGMENT_PAGE, mFragmentPage);
|
||||||
if (fragment instanceof ModDetailFragment) {
|
if (fragment instanceof ModDetailFragment) {
|
||||||
@@ -312,11 +357,16 @@ public class MainActivity extends AppCompatActivity
|
|||||||
return FRAGMENT_MODULES;
|
return FRAGMENT_MODULES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getDefaultFragmentAfterLogout() {
|
||||||
|
return FRAGMENT_CANTEENS;
|
||||||
|
}
|
||||||
|
|
||||||
private void toLogoutState() {
|
private void toLogoutState() {
|
||||||
setOfflineBanner(false);
|
setOfflineBanner(false);
|
||||||
setRefreshFailedBanner(false);
|
setRefreshFailedBanner(false);
|
||||||
updateNavigation();
|
updateNavigation();
|
||||||
changeFragment(FRAGMENT_LOGIN);
|
changeFragment(getDefaultFragmentAfterLogout());
|
||||||
|
mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toLoginState(String fullName, String email, int newFragment, boolean onlineMode) {
|
private void toLoginState(String fullName, String email, int newFragment, boolean onlineMode) {
|
||||||
@@ -351,9 +401,9 @@ public class MainActivity extends AppCompatActivity
|
|||||||
case FRAGMENT_MODULES:
|
case FRAGMENT_MODULES:
|
||||||
fragment = ModulesFragment.newInstance();
|
fragment = ModulesFragment.newInstance();
|
||||||
break;
|
break;
|
||||||
case FRAGMENT_LOGIN:
|
/*case FRAGMENT_LOGIN:
|
||||||
fragment = LoginFragment.newInstance();
|
fragment = LoginFragment.newInstance();
|
||||||
break;
|
break;*/
|
||||||
case FRAGMENT_SCHEDULE:
|
case FRAGMENT_SCHEDULE:
|
||||||
fragment = ScheduleFragment.newInstance();
|
fragment = ScheduleFragment.newInstance();
|
||||||
break;
|
break;
|
||||||
@@ -475,7 +525,7 @@ public class MainActivity extends AppCompatActivity
|
|||||||
if (drawer.isDrawerOpen(GravityCompat.START)) {
|
if (drawer.isDrawerOpen(GravityCompat.START)) {
|
||||||
drawer.closeDrawer(GravityCompat.START);
|
drawer.closeDrawer(GravityCompat.START);
|
||||||
}
|
}
|
||||||
changeFragment(FRAGMENT_LOGIN);
|
mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -526,7 +576,7 @@ public class MainActivity extends AppCompatActivity
|
|||||||
menuItem.setIcon(R.drawable.ic_sms_failed);
|
menuItem.setIcon(R.drawable.ic_sms_failed);
|
||||||
View view = View.inflate(this, R.layout.action_icon_number, null);
|
View view = View.inflate(this, R.layout.action_icon_number, null);
|
||||||
TextView v = view.findViewById(R.id.number);
|
TextView v = view.findViewById(R.id.number);
|
||||||
((TextView) view.findViewById(R.id.number)).setText(String.format(Locale.getDefault(), "%d", i));
|
v.setText(String.format(Locale.getDefault(), "%d", i));
|
||||||
menuItem.setActionView(view);
|
menuItem.setActionView(view);
|
||||||
}
|
}
|
||||||
if (++count[0] == MAX_COUNT) done.run();
|
if (++count[0] == MAX_COUNT) done.run();
|
||||||
@@ -560,6 +610,9 @@ public class MainActivity extends AppCompatActivity
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onModulesFragmentInteraction(final String itemID) {
|
public void onModulesFragmentInteraction(final String itemID) {
|
||||||
changeFragment(FRAGMENT_MODULES_DETAILS, itemID);
|
changeFragment(FRAGMENT_MODULES_DETAILS, itemID);
|
||||||
@@ -646,4 +699,9 @@ public class MainActivity extends AppCompatActivity
|
|||||||
public void onKVVNetworkResponse(NetworkResponse error) {
|
public void onKVVNetworkResponse(NetworkResponse error) {
|
||||||
setRefreshFailedBanner(error != null);
|
setRefreshFailedBanner(error != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomAccountManager getAccountManager() {
|
||||||
|
return mAccountManager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
package de.sebse.fuplanner.fragments;
|
|
||||||
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import de.sebse.fuplanner.R;
|
|
||||||
import de.sebse.fuplanner.tools.MainActivityListener;
|
|
||||||
import de.sebse.fuplanner.tools.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple {@link Fragment} subclass.
|
|
||||||
* Use the {@link LoginFragment#newInstance} factory method to
|
|
||||||
* create an instance of this fragment.
|
|
||||||
*/
|
|
||||||
public class LoginFragment extends Fragment {
|
|
||||||
private final Logger log = new Logger(this);
|
|
||||||
@Nullable private MainActivityListener mActivityListener;
|
|
||||||
|
|
||||||
public LoginFragment() {
|
|
||||||
// Required empty public constructor
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this factory method to create a new instance of
|
|
||||||
* this fragment using the provided parameters.
|
|
||||||
*
|
|
||||||
* @return A new instance of fragment LoginFragment.
|
|
||||||
*/
|
|
||||||
public static LoginFragment newInstance() {
|
|
||||||
LoginFragment fragment = new LoginFragment();
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
View v = inflater.inflate(R.layout.fragment_login, container, false);
|
|
||||||
if (mActivityListener != null && mActivityListener.getKVV().account().isOfflineStoredAvailable()) {
|
|
||||||
Button offline_btn = v.findViewById(R.id.btn_offline);
|
|
||||||
offline_btn.setVisibility(View.VISIBLE);
|
|
||||||
offline_btn.setText(v.getResources().getString(R.string.enter_offline_mode, mActivityListener.getKVV().modules().list().getUsername()));
|
|
||||||
offline_btn.setOnClickListener(v1 -> mActivityListener.getKVV().account().doOfflineLogin());
|
|
||||||
}
|
|
||||||
|
|
||||||
View btn_login = v.findViewById(R.id.btn_login);
|
|
||||||
btn_login.setOnClickListener(view -> {
|
|
||||||
final ProgressDialog progressDialog = new ProgressDialog(LoginFragment.this.getContext(),
|
|
||||||
R.style.FUTheme_Dialog);
|
|
||||||
progressDialog.setIndeterminate(true);
|
|
||||||
progressDialog.setMessage("Authenticating...");
|
|
||||||
progressDialog.show();
|
|
||||||
|
|
||||||
EditText input_usr = ((View) view.getParent()).findViewById(R.id.input_username);
|
|
||||||
EditText input_pwd = ((View) view.getParent()).findViewById(R.id.input_password);
|
|
||||||
|
|
||||||
String username = input_usr.getText().toString();
|
|
||||||
String password = input_pwd.getText().toString();
|
|
||||||
|
|
||||||
mActivityListener.getKVV().account().doOnlineLogin(username, password, success -> {
|
|
||||||
progressDialog.dismiss();
|
|
||||||
mActivityListener.getGoogleAuth().setLoginState(username, password);
|
|
||||||
input_usr.setError(null);
|
|
||||||
input_pwd.setError(null);
|
|
||||||
}, error -> {
|
|
||||||
progressDialog.dismiss();
|
|
||||||
// Invalid password
|
|
||||||
if (mActivityListener != null) {
|
|
||||||
if (error.getCode() == 100131) {
|
|
||||||
mActivityListener.showToast(R.string.invalid_credentials);
|
|
||||||
input_usr.setError(input_usr.getResources().getString(R.string.invalid_credentials));
|
|
||||||
input_pwd.setError(input_pwd.getResources().getString(R.string.invalid_credentials));
|
|
||||||
} else {
|
|
||||||
mActivityListener.showToast(v.getResources().getString(R.string.error_occurred_code, error.getCode()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.e("Error on KVV login!", error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
if (context instanceof MainActivityListener) {
|
|
||||||
mActivityListener = (MainActivityListener) context;
|
|
||||||
mActivityListener.onTitleTextChange(R.string.log_in);
|
|
||||||
} else
|
|
||||||
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDetach() {
|
|
||||||
super.onDetach();
|
|
||||||
mActivityListener = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import com.android.volley.NetworkResponse;
|
|||||||
|
|
||||||
import de.sebse.fuplanner.services.GoogleAuth.Credentials;
|
import de.sebse.fuplanner.services.GoogleAuth.Credentials;
|
||||||
import de.sebse.fuplanner.services.KVV.types.LoginToken;
|
import de.sebse.fuplanner.services.KVV.types.LoginToken;
|
||||||
|
import de.sebse.fuplanner.tools.CustomAccountManager;
|
||||||
import de.sebse.fuplanner.tools.network.NetworkCallback;
|
import de.sebse.fuplanner.tools.network.NetworkCallback;
|
||||||
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
|
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
|
||||||
|
|
||||||
@@ -17,4 +18,6 @@ public interface KVVListener {
|
|||||||
void onModuleListChange();
|
void onModuleListChange();
|
||||||
|
|
||||||
void onKVVNetworkResponse(NetworkResponse error);
|
void onKVVNetworkResponse(NetworkResponse error);
|
||||||
|
|
||||||
|
CustomAccountManager getAccountManager();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,14 @@ package de.sebse.fuplanner.services.KVV;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import de.sebse.fuplanner.services.KVV.types.LoginToken;
|
import de.sebse.fuplanner.services.KVV.types.LoginToken;
|
||||||
|
import de.sebse.fuplanner.services.newkvv.AccountGeneral;
|
||||||
|
import de.sebse.fuplanner.tools.CustomAccountManager;
|
||||||
import de.sebse.fuplanner.tools.NetworkCallbackCollector;
|
import de.sebse.fuplanner.tools.NetworkCallbackCollector;
|
||||||
import de.sebse.fuplanner.tools.network.HTTPService;
|
import de.sebse.fuplanner.tools.network.HTTPService;
|
||||||
import de.sebse.fuplanner.tools.network.NetworkCallback;
|
import de.sebse.fuplanner.tools.network.NetworkCallback;
|
||||||
@@ -33,63 +28,43 @@ public class Login extends HTTPService {
|
|||||||
this.mListener = listener;
|
this.mListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void reset() {
|
||||||
|
mToken = null;
|
||||||
|
mLoginPending = false;
|
||||||
|
mOnlineMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
public void doOnlineLogin(@NotNull String username, @NotNull String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback errorCallback) {
|
public void doOnlineLogin(@NotNull String username, @NotNull String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback errorCallback) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//public boolean restoreOnlineLogin() {
|
||||||
|
// return restoreLogin(true);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public boolean doOfflineLogin() {
|
||||||
|
// return restoreLogin(false);
|
||||||
|
//}
|
||||||
|
|
||||||
|
public void restoreOnlineLogin(BooleanInterface callback) {
|
||||||
if (mLoginPending) {
|
if (mLoginPending) {
|
||||||
errorCallback.onError(new NetworkError(100160, -1, "Login already pending!"));
|
callback.run(false);
|
||||||
log.t();
|
return;
|
||||||
}
|
}
|
||||||
mLoginPending = true;
|
mLoginPending = true;
|
||||||
doLogin(username, password, token -> {
|
LoginToken.load(mListener.getAccountManager(), token -> {
|
||||||
testLoginToken(token, token2 -> {
|
boolean result = setToken(token, true);
|
||||||
setToken(token2, true);
|
|
||||||
mLoginPending = false;
|
mLoginPending = false;
|
||||||
callback.onResponse(token2);
|
log.d("loginToken", token != null ? token.toString() : null);
|
||||||
}, error -> {
|
callback.run(result);
|
||||||
mLoginPending = false;
|
|
||||||
errorCallback.onError(error);
|
|
||||||
});
|
|
||||||
}, error -> {
|
|
||||||
mLoginPending = false;
|
|
||||||
errorCallback.onError(error);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean restoreOnlineLogin() {
|
public void isOfflineStoredAvailable(BooleanInterface callback) {
|
||||||
return restoreLogin(true);
|
LoginToken.load(mListener.getAccountManager(), token -> {
|
||||||
}
|
callback.run(token != null);
|
||||||
|
});
|
||||||
public boolean doOfflineLogin() {
|
|
||||||
return restoreLogin(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean restoreLogin(boolean enteringOnlineMode) {
|
|
||||||
if (mLoginPending || mToken != null)
|
|
||||||
return false;
|
|
||||||
mLoginPending = true;
|
|
||||||
boolean result = false;
|
|
||||||
try {
|
|
||||||
result = setToken(LoginToken.load(getContext()), enteringOnlineMode);
|
|
||||||
} catch (FileNotFoundException ignored) {
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
mLoginPending = false;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOfflineStoredAvailable() {
|
|
||||||
try {
|
|
||||||
LoginToken load = LoginToken.load(getContext());
|
|
||||||
return load != null;
|
|
||||||
} catch (FileNotFoundException ignored) {
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean logout(boolean delete) {
|
public boolean logout(boolean delete) {
|
||||||
@@ -98,7 +73,7 @@ public class Login extends HTTPService {
|
|||||||
if (mToken == null)
|
if (mToken == null)
|
||||||
return true;
|
return true;
|
||||||
if (delete)
|
if (delete)
|
||||||
mToken.delete(getContext());
|
mToken.delete(mListener.getAccountManager());
|
||||||
mToken = null;
|
mToken = null;
|
||||||
return handleCallbacks();
|
return handleCallbacks();
|
||||||
}
|
}
|
||||||
@@ -155,14 +130,27 @@ public class Login extends HTTPService {
|
|||||||
mRefreshCallbacks.add(success, error);
|
mRefreshCallbacks.add(success, error);
|
||||||
if (!isFirst)
|
if (!isFirst)
|
||||||
return;
|
return;
|
||||||
mListener.getCredentials(credentials -> {
|
CustomAccountManager manager = mListener.getAccountManager();
|
||||||
|
manager.doInvalidateToken(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, ignored -> {
|
||||||
|
log.d("try restore", ignored);
|
||||||
|
restoreOnlineLogin(isRestored -> {
|
||||||
|
log.d("restore", isRestored, mToken);
|
||||||
|
if (isRestored)
|
||||||
|
testLoginToken(mRefreshCallbacks::responseResponse, mRefreshCallbacks::responseError);
|
||||||
|
else {
|
||||||
|
logout(true);
|
||||||
|
mRefreshCallbacks.responseError(new NetworkError(100180, 403, "Re-login failed!"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
/* mListener.getCredentials(credentials -> {
|
||||||
doOnlineLogin(credentials.getUsername(), credentials.getPassword(),
|
doOnlineLogin(credentials.getUsername(), credentials.getPassword(),
|
||||||
mRefreshCallbacks::responseResponse,
|
mRefreshCallbacks::responseResponse,
|
||||||
mRefreshCallbacks::responseError);
|
mRefreshCallbacks::responseError);
|
||||||
}, e -> {
|
}, e -> {
|
||||||
logout(false);
|
logout(false);
|
||||||
mRefreshCallbacks.responseError(e);
|
mRefreshCallbacks.responseError(e);
|
||||||
});
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -182,272 +170,11 @@ public class Login extends HTTPService {
|
|||||||
return false;
|
return false;
|
||||||
boolean isOnlyRefresh = mToken != null;
|
boolean isOnlyRefresh = mToken != null;
|
||||||
mToken = token;
|
mToken = token;
|
||||||
if (enteringOnlineMode) {
|
|
||||||
try {
|
|
||||||
mToken.save(getContext());
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mOnlineMode = enteringOnlineMode;
|
mOnlineMode = enteringOnlineMode;
|
||||||
return isOnlyRefresh || handleCallbacks();
|
return isOnlyRefresh || handleCallbacks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface BooleanInterface {
|
||||||
|
void run(boolean bool);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void doLogin(String username, String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback error) {
|
|
||||||
startKVVSession(success -> {
|
|
||||||
String kvvJSESSIONID = success.get("JSESSIONID");
|
|
||||||
getSAMLRequest(kvvJSESSIONID, success1 -> startIdentSession(success1.get("Location"), success11 -> {
|
|
||||||
String identJSESSIONID = success11.get("JSESSIONID");
|
|
||||||
String ident_idp_authn_lc_key = success11.get("_idp_authn_lc_key");
|
|
||||||
String identROUTEID = success11.get("ROUTEID");
|
|
||||||
loginIdent(true, username, password, identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, success111 -> loginIdent(false, username, password, identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, success11112 -> {
|
|
||||||
String ident_idp_session = success11112.get("_idp_session");
|
|
||||||
getSAMLResponse(identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, ident_idp_session, success1111 -> loginKVV(success1111.get("RelayState"), success1111.get("SAMLResponse"), kvvJSESSIONID, success111112 -> {
|
|
||||||
LoginToken token = new LoginToken(username, success111112.get("shibsessionKey"), success111112.get("shibsessionName"), kvvJSESSIONID);
|
|
||||||
finishKVVlogin(token, success11111 -> callback.onResponse(token), error);
|
|
||||||
}, error), error);
|
|
||||||
}, error), error);
|
|
||||||
}, error), error);
|
|
||||||
}, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
GET https://kvv.imp.fu-berlin.de/portal/login
|
|
||||||
-> JSESSIONID 5c10406f-588c-4c16-96e9-c80d115417de.tomcat1
|
|
||||||
*/
|
|
||||||
private void startKVVSession(final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
|
||||||
get("https://kvv.imp.fu-berlin.de/portal/login", null, response -> {
|
|
||||||
String cookies = response.getHeaders().get("Set-Cookie");
|
|
||||||
if (cookies==null) {
|
|
||||||
errorCallback.onError(new NetworkError(100101, -1, "Error on starting KVV session!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
HashMap<String, String> object;
|
|
||||||
try {
|
|
||||||
object = getCookie(cookies, new String[]{"JSESSIONID"});
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
errorCallback.onError(new NetworkError(100102, -1, "Error on starting KVV session!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback.onResponse(object);
|
|
||||||
}, error -> errorCallback.onError(new NetworkError(100100, error.networkResponse.statusCode, "Error on starting KVV session!")));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
GET https://kvv.imp.fu-berlin.de/sakai-login-tool/container
|
|
||||||
<- JSESSIONID
|
|
||||||
-> (Location-Header) https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
|
|
||||||
?SAMLRequest=fZLLb.....Q8yre3X1IHwkJKE0Mnpy/V9TH4A
|
|
||||||
&RelayState=ss:mem:7ea01e29157b8bd906f7002176.....0d1a505f2c8bf
|
|
||||||
*/
|
|
||||||
private void getSAMLRequest(String JSESSIONID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
|
||||||
HashMap<String, String> cookies = new HashMap<>();
|
|
||||||
cookies.put("JSESSIONID", JSESSIONID);
|
|
||||||
get("https://kvv.imp.fu-berlin.de/sakai-login-tool/container", cookies, response -> {
|
|
||||||
String location = response.getHeaders().get("Location");
|
|
||||||
if (location==null) {
|
|
||||||
errorCallback.onError(new NetworkError(100111, -1, "Error on getting SAML request!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
HashMap<String, String> object = new HashMap<>();
|
|
||||||
object.put("Location", location);
|
|
||||||
callback.onResponse(object);
|
|
||||||
}, error -> errorCallback.onError(new NetworkError(100110, error.networkResponse.statusCode, "Error on getting SAML request!")));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
GET https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
|
|
||||||
?SAMLRequest=fZLLbsIwEEV/JfI+cWJAUIsgpbAoEi2IpF10UznxUKw6dupxaPn7hkdb2LD29bkzRzNGUeuGZ63fmjV8toA++K61QX58SEnrDLcCFXIjakDuK55njwvOopg3znpbWU2CDBGcV9ZMrcG2BpeD26kKnteLlGy9b5BT+rHbRapuok0bluC0MpEEmm9VWVoNfhshWnpgM7pa5gUJZt0wyogD9h+iJBiv/P6aomQTbtqSdhNtlIYzZg1SOag8zfMlCeazlLyNqpHsy1gO2V1fVsNBMuqJoUyAJaxXDUaiiyG2MDfohfEpYXEyDJM4ZKxgCe/FPI5fSbA6L36vjFTm/bal8hRC/lAUq/C02gs4PK7VBchkfHDNj8Xuwv5trPhVTiY3BeOf4DG96DmVNvypA89nK6tVtQ8yre3X1IHwkJKE0Mnpy/V9TH4A
|
|
||||||
&RelayState=ss:mem:7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
|
|
||||||
-> JSESSIONID C4B6A428BA1F50746235D03F5D107A57
|
|
||||||
-> _idp_authn_lc_key 57a6ae26067f374cc3d0ccfc47e27b04b47752d2a3d4eb2782af0d3994535395
|
|
||||||
-> ROUTEID .1
|
|
||||||
*/
|
|
||||||
private void startIdentSession(String url, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
|
||||||
get(url, null, response -> {
|
|
||||||
String cookies = response.getHeaders().get("Set-Cookie");
|
|
||||||
if (cookies==null) {
|
|
||||||
errorCallback.onError(new NetworkError(100121, -1, "Error on starting Ident session!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
HashMap<String, String> object;
|
|
||||||
try {
|
|
||||||
object = getCookie(cookies, new String[]{"JSESSIONID", "_idp_authn_lc_key", "ROUTEID"});
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
errorCallback.onError(new NetworkError(100122, -1, "Error on starting Ident session!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback.onResponse(object);
|
|
||||||
}, error -> errorCallback.onError(new NetworkError(100120, error.networkResponse.statusCode, "Error on starting Ident session!")));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
POST https://identity.fu-berlin.de/idp-fub/Authn/UserPassword
|
|
||||||
<- j_username seedorf96
|
|
||||||
<- j_password neinhieristpatrick
|
|
||||||
<- (Header-"Content-Type") application/x-www-form-urlencoded
|
|
||||||
<- JSESSIONID
|
|
||||||
<- _idp_authn_lc_key
|
|
||||||
<- ROUTEID
|
|
||||||
-> _idp_session OTMuMTkzLjg1LjMz|LQ==|OGYxOWI4MjA2NTQ4YWUwYzJkOWM4Mjk4YzcwZDMwZmJiZjBmMTdmMzkyZGU2OWIwY2JkNmZlNjlmNTRmNzBlMQ==|wLlzQal7VqyntmG2vLNn06wt8wQ=
|
|
||||||
*/
|
|
||||||
private void loginIdent(final boolean first, String username, String password, String JSESSIONID, String _idp_authn_lc_key, String ROUTEID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
|
||||||
HashMap<String, String> cookies = new HashMap<>();
|
|
||||||
cookies.put("JSESSIONID", JSESSIONID);
|
|
||||||
cookies.put("_idp_authn_lc_key", _idp_authn_lc_key);
|
|
||||||
cookies.put("ROUTEID", ROUTEID);
|
|
||||||
HashMap<String, String> body = new HashMap<>();
|
|
||||||
body.put("j_username", username);
|
|
||||||
body.put("j_password", password);
|
|
||||||
post("https://identity.fu-berlin.de/idp-fub/Authn/UserPassword", cookies, body, response -> {
|
|
||||||
if (first) {
|
|
||||||
callback.onResponse(new HashMap<>());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String cookies1 = response.getHeaders().get("Set-Cookie");
|
|
||||||
if (cookies1 ==null) {
|
|
||||||
errorCallback.onError(new NetworkError(100131, -1, "Error on logging in to Identity Server!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
HashMap<String, String> object;
|
|
||||||
try {
|
|
||||||
object = getCookie(cookies1, new String[]{"_idp_session"});
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
errorCallback.onError(new NetworkError(100132, -1, "Error on logging in to Identity Server!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback.onResponse(object);
|
|
||||||
}, error -> errorCallback.onError(new NetworkError(100130, error.networkResponse.statusCode, "Error on logging in to Identity Server!")));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
GET https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
|
|
||||||
<- JSESSIONID
|
|
||||||
<- _idp_authn_lc_key
|
|
||||||
<- ROUTEID
|
|
||||||
<- _idp_session
|
|
||||||
-> (BODY) RelayState 7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
|
|
||||||
-> (BODY) SAMLResponse PD94bWwgdmVyc2lvbj0...........wvc2FtbDJwOlJlc3BvbnNlPg==
|
|
||||||
*/
|
|
||||||
private void getSAMLResponse(String JSESSIONID, String _idp_authn_lc_key, String ROUTEID, String _idp_session, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
|
||||||
HashMap<String, String> cookies = new HashMap<>();
|
|
||||||
cookies.put("JSESSIONID", JSESSIONID);
|
|
||||||
cookies.put("_idp_authn_lc_key", _idp_authn_lc_key);
|
|
||||||
cookies.put("ROUTEID", ROUTEID);
|
|
||||||
cookies.put("_idp_session", _idp_session);
|
|
||||||
get("https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO", cookies, response -> {
|
|
||||||
String body = response.getParsed();
|
|
||||||
if (body == null) {
|
|
||||||
errorCallback.onError(new NetworkError(100143, -1, "Error on getting SAML response!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HashMap<String, String> object = new HashMap<>();
|
|
||||||
|
|
||||||
Pattern pattern = Pattern.compile("ss:mem:([0-9a-f]+)");
|
|
||||||
Matcher matcher = pattern.matcher(body);
|
|
||||||
if (!matcher.find()) {
|
|
||||||
errorCallback.onError(new NetworkError(100142, -1, "Error on getting SAML response!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
object.put("RelayState", "ss:mem:"+matcher.group(1));
|
|
||||||
|
|
||||||
pattern = Pattern.compile("name=\"SAMLResponse\" value=\"([0-9a-zA-Z+]+=*)");
|
|
||||||
matcher = pattern.matcher(body);
|
|
||||||
if (!matcher.find()) {
|
|
||||||
errorCallback.onError(new NetworkError(100141, -1, "Error on getting SAML response!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
object.put("SAMLResponse", matcher.group(1));
|
|
||||||
|
|
||||||
callback.onResponse(object);
|
|
||||||
}, error -> errorCallback.onError(new NetworkError(100140, error.networkResponse.statusCode, "Error on getting SAML response!")));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
POST https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST
|
|
||||||
<- RelayState 7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
|
|
||||||
<- SAMLResponse PD94bWwgdmVyc2lvbj0...........wvc2FtbDJwOlJlc3BvbnNlPg==
|
|
||||||
<- JSESSIONID
|
|
||||||
-> _shibsession_64656661756c7468747470733a2f2f6b76762e696d702e66752d6265726c696e2e64652f73686962626f6c657468
|
|
||||||
_b1912c5a03d733a80bd3fee772bf68d4
|
|
||||||
*/
|
|
||||||
private void loginKVV(String RelayState, String SAMLResponse, String JSESSIONID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
|
||||||
HashMap<String, String> cookies = new HashMap<>();
|
|
||||||
cookies.put("JSESSIONID", JSESSIONID);
|
|
||||||
HashMap<String, String> body = new HashMap<>();
|
|
||||||
body.put("RelayState", RelayState);
|
|
||||||
body.put("SAMLResponse", SAMLResponse);
|
|
||||||
post("https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST", cookies, body, response -> {
|
|
||||||
String cookies1 = response.getHeaders().get("Set-Cookie");
|
|
||||||
if (cookies1 ==null) {
|
|
||||||
errorCallback.onError(new NetworkError(100151, -1, "Error on starting KVV session!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
HashMap<String, String> object = new HashMap<>();
|
|
||||||
|
|
||||||
|
|
||||||
Pattern pattern = Pattern.compile("(_shibsession_[0-9a-f]+)=([^;]+);");
|
|
||||||
Matcher matcher = pattern.matcher(cookies1);
|
|
||||||
if (!matcher.find()) {
|
|
||||||
errorCallback.onError(new NetworkError(100152, -1, "Error on starting Ident session!"));
|
|
||||||
}
|
|
||||||
object.put("shibsessionKey", matcher.group(1));
|
|
||||||
object.put("shibsessionName", matcher.group(2));
|
|
||||||
|
|
||||||
callback.onResponse(object);
|
|
||||||
}, error -> errorCallback.onError(new NetworkError(100150, error.networkResponse.statusCode, "Error on starting Ident session!")));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
GET https://kvv.imp.fu-berlin.de/sakai-login-tool/container
|
|
||||||
<- JSESSIONID
|
|
||||||
<- _shibsession_64656661756c7468747470733a2f2f6b76762e696d702e66752d6265726c696e2e64652f73686962626f6c657468
|
|
||||||
_b1912c5a03d733a80bd3fee772bf68d4
|
|
||||||
*/
|
|
||||||
private void finishKVVlogin(LoginToken loginToken, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
|
||||||
get("https://kvv.imp.fu-berlin.de/sakai-login-tool/container", loginToken.getCookies(), response -> callback.onResponse(new HashMap<>()), error -> errorCallback.onError(new NetworkError(100160, error.networkResponse.statusCode, "Cannot finish login process!")));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private String getCookie(String cookies, String name) throws NoSuchFieldException {
|
|
||||||
Pattern pattern = Pattern.compile(name+"=([^;]+);");
|
|
||||||
Matcher matcher = pattern.matcher(cookies);
|
|
||||||
if (!matcher.find()) {
|
|
||||||
log.e("GETcookie failed", name);
|
|
||||||
log.e("GETcookie failed", cookies);
|
|
||||||
throw new NoSuchFieldException();
|
|
||||||
}
|
|
||||||
return matcher.group(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HashMap<String, String> getCookie(String cookies, String[] names) throws NoSuchFieldException {
|
|
||||||
HashMap<String, String> result = new HashMap<>();
|
|
||||||
for (String name: names) {
|
|
||||||
result.put(name,this.getCookie(cookies, name));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,23 +2,25 @@ package de.sebse.fuplanner.services.KVV.types;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import org.json.JSONException;
|
||||||
import java.io.FileNotFoundException;
|
import org.json.JSONObject;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import de.sebse.fuplanner.services.newkvv.AccountGeneral;
|
||||||
|
import de.sebse.fuplanner.tools.CustomAccountManager;
|
||||||
|
import de.sebse.fuplanner.tools.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by sebastian on 29.10.17.
|
* Created by sebastian on 29.10.17.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class LoginToken implements Serializable {
|
public class LoginToken implements Serializable {
|
||||||
|
static Logger log = new Logger("LoginToken");
|
||||||
private static final String FILE_NAME = "LoginTokenSaving";
|
private static final String FILE_NAME = "LoginTokenSaving";
|
||||||
|
|
||||||
private final String username;
|
private final String username;
|
||||||
@@ -36,33 +38,35 @@ public class LoginToken implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static LoginToken load(Context context) throws IOException, ClassNotFoundException {
|
public static void load(CustomAccountManager manager, LoginTokenInterface callback) {
|
||||||
FileInputStream fis;
|
log.d("try to login");
|
||||||
try {
|
if (!manager.hasAccounts(AccountGeneral.ACCOUNT_TYPE)) {
|
||||||
fis = context.openFileInput(FILE_NAME);
|
callback.run(null);
|
||||||
} catch (FileNotFoundException e) {
|
return;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
ObjectInputStream is = new ObjectInputStream(fis);
|
log.d("has accounts");
|
||||||
Object readObject = is.readObject();
|
manager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, tokenString -> {
|
||||||
if (!(readObject instanceof LoginToken))
|
//manager.getTokenByTypeSync(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV);
|
||||||
return null;
|
if (tokenString == null) {
|
||||||
LoginToken loginToken = (LoginToken) readObject;
|
callback.run(null);
|
||||||
is.close();
|
return;
|
||||||
fis.close();
|
}
|
||||||
return loginToken;
|
log.d("got token string", tokenString);
|
||||||
|
callback.run(LoginToken.fromJsonString(tokenString));
|
||||||
|
//return LoginToken.fromJsonString(tokenString);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(Context context) throws IOException {
|
public void save(Context context) throws IOException {
|
||||||
FileOutputStream fos = context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
|
/*FileOutputStream fos = context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
|
||||||
ObjectOutputStream os = new ObjectOutputStream(fos);
|
ObjectOutputStream os = new ObjectOutputStream(fos);
|
||||||
os.writeObject(this);
|
os.writeObject(this);
|
||||||
os.close();
|
os.close();
|
||||||
fos.close();
|
fos.close();*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(Context context) {
|
public void delete(CustomAccountManager manager) {
|
||||||
context.deleteFile(FILE_NAME);
|
manager.deleteAccount(AccountGeneral.ACCOUNT_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAdditionals(String fullName, String email) {
|
public void setAdditionals(String fullName, String email) {
|
||||||
@@ -118,4 +122,43 @@ public class LoginToken implements Serializable {
|
|||||||
}
|
}
|
||||||
return result.substring(0, result.length()-1);
|
return result.substring(0, result.length()-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toJsonString() {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
try {
|
||||||
|
json.put("username", username);
|
||||||
|
json.put("shibsessionKey", shibsessionKey);
|
||||||
|
json.put("shibsessionName", shibsessionName);
|
||||||
|
json.put("JSESSIONID", JSESSIONID);
|
||||||
|
json.put("fullName", fullName);
|
||||||
|
json.put("email", email);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return json.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LoginToken fromJsonString(String tokenString) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject(tokenString);
|
||||||
|
LoginToken token = new LoginToken(
|
||||||
|
json.getString("username"),
|
||||||
|
json.getString("shibsessionName"),
|
||||||
|
json.getString("shibsessionName"),
|
||||||
|
json.getString("JSESSIONID"));
|
||||||
|
if (!json.isNull("fullName"))
|
||||||
|
token.setAdditionals(
|
||||||
|
json.getString("fullName"),
|
||||||
|
json.getString("email")
|
||||||
|
);
|
||||||
|
return token;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LoginTokenInterface {
|
||||||
|
void run(LoginToken token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package de.sebse.fuplanner.services.newkvv;
|
||||||
|
|
||||||
|
public class AccountGeneral {
|
||||||
|
public static final String ACCOUNT_TYPE = "de.sebse.fuplanner.fuauth";
|
||||||
|
public static final String AUTHTOKEN_TYPE_KVV = "KVV";
|
||||||
|
public static final String AUTHTOKEN_TYPE_BLACKBOARD = "Blackboard";
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package de.sebse.fuplanner.services.newkvv;
|
||||||
|
|
||||||
|
import android.accounts.AbstractAccountAuthenticator;
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.accounts.AccountAuthenticatorResponse;
|
||||||
|
import android.accounts.AccountManager;
|
||||||
|
import android.accounts.NetworkErrorException;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
public class FUAuthenticator extends AbstractAccountAuthenticator {
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
public FUAuthenticator(Context context) {
|
||||||
|
super(context);
|
||||||
|
this.mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse, String s) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
|
||||||
|
final Intent intent = new Intent(mContext, FUAuthenticatorActivity.class);
|
||||||
|
intent.putExtra(FUAuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType);
|
||||||
|
intent.putExtra(FUAuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
|
||||||
|
intent.putExtra(FUAuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);
|
||||||
|
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
||||||
|
final Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, Bundle bundle) throws NetworkErrorException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
||||||
|
|
||||||
|
// Extract the username and password from the Account Manager, and ask
|
||||||
|
// the server for an appropriate AuthToken.
|
||||||
|
final AccountManager am = AccountManager.get(mContext);
|
||||||
|
|
||||||
|
String authToken = am.peekAuthToken(account, authTokenType);
|
||||||
|
|
||||||
|
// Lets give another try to authenticate the user
|
||||||
|
if (TextUtils.isEmpty(authToken)) {
|
||||||
|
final String password = am.getPassword(account);
|
||||||
|
if (password != null) {
|
||||||
|
try {
|
||||||
|
authToken = new UserLoginTask(account.name, password, authTokenType, mContext).execute((Void) null).get();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get an authToken - we return it
|
||||||
|
if (!TextUtils.isEmpty(authToken)) {
|
||||||
|
final Bundle result = new Bundle();
|
||||||
|
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
|
||||||
|
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
|
||||||
|
result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, then we couldn't access the user's password - so we
|
||||||
|
// need to re-prompt them for their credentials. We do that by creating
|
||||||
|
// an intent to display our AuthenticatorActivity.
|
||||||
|
final Intent intent = new Intent(mContext, FUAuthenticatorActivity.class);
|
||||||
|
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
||||||
|
intent.putExtra(FUAuthenticatorActivity.ARG_ACCOUNT_TYPE, account.type);
|
||||||
|
intent.putExtra(FUAuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
|
||||||
|
final Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthTokenLabel(String s) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle bundle) throws NetworkErrorException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String[] strings) throws NetworkErrorException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
package de.sebse.fuplanner.services.newkvv;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.accounts.AccountAuthenticatorActivity;
|
||||||
|
import android.accounts.AccountManager;
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.LoaderManager.LoaderCallbacks;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.CursorLoader;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Loader;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.provider.ContactsContract;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import de.sebse.fuplanner.R;
|
||||||
|
|
||||||
|
import static de.sebse.fuplanner.services.newkvv.UserLoginTask.PARAM_USER_PASS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A login screen that offers login via email/password.
|
||||||
|
*/
|
||||||
|
public class FUAuthenticatorActivity extends AccountAuthenticatorActivity implements LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Id to identity READ_CONTACTS permission request.
|
||||||
|
*/
|
||||||
|
private static final int REQUEST_READ_CONTACTS = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep track of the login task to ensure we can cancel it if requested.
|
||||||
|
*/
|
||||||
|
UserLoginTask mAuthTask = null;
|
||||||
|
|
||||||
|
// UI references.
|
||||||
|
private EditText mEmailView;
|
||||||
|
EditText mPasswordView;
|
||||||
|
private View mProgressView;
|
||||||
|
private View mLoginFormView;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static final String ARG_ACCOUNT_TYPE = "ARG_ACCOUNT_TYPE";
|
||||||
|
public static final String ARG_AUTH_TYPE = "ARG_AUTH_TYPE";
|
||||||
|
public static final String ARG_IS_ADDING_NEW_ACCOUNT = "ARG_IS_ADDING_NEW_ACCOUNT";
|
||||||
|
private String mAccountType;
|
||||||
|
private String mAuthTokenType;
|
||||||
|
private boolean mIsAddingNewAccount;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
mAccountType = getIntent().getStringExtra(ARG_ACCOUNT_TYPE);
|
||||||
|
mAuthTokenType = getIntent().getStringExtra(ARG_AUTH_TYPE);
|
||||||
|
mIsAddingNewAccount = getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_fu_authenticator);
|
||||||
|
// Set up the login form.
|
||||||
|
mEmailView = findViewById(R.id.input_username);
|
||||||
|
populateAutoComplete();
|
||||||
|
|
||||||
|
mPasswordView = findViewById(R.id.input_password);
|
||||||
|
mPasswordView.setOnEditorActionListener((textView, id, keyEvent) -> {
|
||||||
|
if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
|
||||||
|
attemptLogin();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
Button mEmailSignInButton = findViewById(R.id.btn_login);
|
||||||
|
mEmailSignInButton.setOnClickListener(view -> attemptLogin());
|
||||||
|
|
||||||
|
mLoginFormView = findViewById(R.id.login_form);
|
||||||
|
mProgressView = findViewById(R.id.login_progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateAutoComplete() {
|
||||||
|
if (!mayRequestContacts()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLoaderManager().initLoader(0, null, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean mayRequestContacts() {
|
||||||
|
/*if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
|
||||||
|
Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
|
||||||
|
.setAction(android.R.string.ok, new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
public void onClick(View v) {
|
||||||
|
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
|
||||||
|
}*/
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback received when a permissions request has been completed.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||||
|
@NonNull int[] grantResults) {
|
||||||
|
if (requestCode == REQUEST_READ_CONTACTS) {
|
||||||
|
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
populateAutoComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to sign in or register the account specified by the login form.
|
||||||
|
* If there are form errors (invalid email, missing fields, etc.), the
|
||||||
|
* errors are presented and no actual login attempt is made.
|
||||||
|
*/
|
||||||
|
private void attemptLogin() {
|
||||||
|
InputMethodManager inputManager = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
if (inputManager != null && getCurrentFocus() != null)
|
||||||
|
inputManager.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||||
|
if (mAuthTask != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset errors.
|
||||||
|
mEmailView.setError(null);
|
||||||
|
mPasswordView.setError(null);
|
||||||
|
|
||||||
|
// Store values at the time of the login attempt.
|
||||||
|
String email = mEmailView.getText().toString();
|
||||||
|
String password = mPasswordView.getText().toString();
|
||||||
|
|
||||||
|
boolean cancel = false;
|
||||||
|
View focusView = null;
|
||||||
|
|
||||||
|
// Check for a valid password, if the user entered one.
|
||||||
|
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
|
||||||
|
mPasswordView.setError(getString(R.string.error_invalid_password));
|
||||||
|
focusView = mPasswordView;
|
||||||
|
cancel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a valid email address.
|
||||||
|
if (TextUtils.isEmpty(email)) {
|
||||||
|
mEmailView.setError(getString(R.string.error_field_required));
|
||||||
|
focusView = mEmailView;
|
||||||
|
cancel = true;
|
||||||
|
} else if (!isEmailValid(email)) {
|
||||||
|
mEmailView.setError(getString(R.string.error_invalid_email));
|
||||||
|
focusView = mEmailView;
|
||||||
|
cancel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
// There was an error; don't attempt login and focus the first
|
||||||
|
// form field with an error.
|
||||||
|
focusView.requestFocus();
|
||||||
|
} else {
|
||||||
|
// Show a progress spinner, and kick off a background task to
|
||||||
|
// perform the user login attempt.
|
||||||
|
showProgress(true);
|
||||||
|
mAuthTask = new UserLoginTask(email, password, mAuthTokenType, this);
|
||||||
|
mAuthTask.execute((Void) null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEmailValid(String email) {
|
||||||
|
//TODO: Replace this with your own logic
|
||||||
|
return true;//email.contains("@");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPasswordValid(String password) {
|
||||||
|
//TODO: Replace this with your own logic
|
||||||
|
return password.length() > 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the progress UI and hides the login form.
|
||||||
|
*/
|
||||||
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
|
||||||
|
void showProgress(final boolean show) {
|
||||||
|
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
|
||||||
|
// for very easy animations. If available, use these APIs to fade-in
|
||||||
|
// the progress spinner.
|
||||||
|
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
|
||||||
|
|
||||||
|
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
|
||||||
|
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
|
||||||
|
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
|
mProgressView.animate().setDuration(shortAnimTime).alpha(
|
||||||
|
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||||
|
return new CursorLoader(this,
|
||||||
|
// Retrieve data rows for the device user's 'profile' contact.
|
||||||
|
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
|
||||||
|
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
|
||||||
|
|
||||||
|
// Select only email addresses.
|
||||||
|
ContactsContract.Contacts.Data.MIMETYPE +
|
||||||
|
" = ?", new String[]{ContactsContract.CommonDataKinds.Email
|
||||||
|
.CONTENT_ITEM_TYPE},
|
||||||
|
|
||||||
|
// Show primary email addresses first. Note that there won't be
|
||||||
|
// a primary email address if the user hasn't specified one.
|
||||||
|
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private interface ProfileQuery {
|
||||||
|
String[] PROJECTION = {
|
||||||
|
ContactsContract.CommonDataKinds.Email.ADDRESS,
|
||||||
|
ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
|
||||||
|
};
|
||||||
|
|
||||||
|
int ADDRESS = 0;
|
||||||
|
int IS_PRIMARY = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishLogin(Intent intent) {
|
||||||
|
String accountName = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
|
||||||
|
String accountPassword = intent.getStringExtra(PARAM_USER_PASS);
|
||||||
|
final Account account = new Account(accountName, intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE));
|
||||||
|
final AccountManager mAccountManager = AccountManager.get(this);
|
||||||
|
|
||||||
|
if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false)) {
|
||||||
|
String authtoken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN);
|
||||||
|
String authtokenType = mAuthTokenType;
|
||||||
|
|
||||||
|
// Creating the account on the device and setting the auth token we got
|
||||||
|
// (Not setting the auth token will cause another call to the server to authenticate the user)
|
||||||
|
mAccountManager.addAccountExplicitly(account, accountPassword, null);
|
||||||
|
mAccountManager.setAuthToken(account, authtokenType, authtoken);
|
||||||
|
} else {
|
||||||
|
mAccountManager.setPassword(account, accountPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAccountAuthenticatorResult(intent.getExtras());
|
||||||
|
setResult(RESULT_OK, intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package de.sebse.fuplanner.services.newkvv;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
public class FUAuthenticatorService extends Service {
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
FUAuthenticator authenticator = new FUAuthenticator(this);
|
||||||
|
return authenticator.getIBinder();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package de.sebse.fuplanner.services.newkvv;
|
||||||
|
|
||||||
|
import android.accounts.AccountManager;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import de.sebse.fuplanner.R;
|
||||||
|
import de.sebse.fuplanner.services.KVV.types.LoginToken;
|
||||||
|
import de.sebse.fuplanner.services.newkvv.network.Login;
|
||||||
|
import de.sebse.fuplanner.tools.logging.Logger;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an asynchronous login/registration task used to authenticate
|
||||||
|
* the user.
|
||||||
|
*/
|
||||||
|
public class UserLoginTask extends AsyncTask<Void, Void, String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dummy authentication store containing known user names and passwords.
|
||||||
|
* TODO: remove after connecting to a real authentication system.
|
||||||
|
*/
|
||||||
|
/*private static final String[] DUMMY_CREDENTIALS = new String[]{
|
||||||
|
"foo@example.com:hello", "bar@example.com:world"
|
||||||
|
};*/
|
||||||
|
static final String PARAM_USER_PASS = "PARAM_USER_PASS";
|
||||||
|
|
||||||
|
private final String mEmail;
|
||||||
|
private final String mPassword;
|
||||||
|
private final Login mVolleyLogin;
|
||||||
|
private String mTokenType;
|
||||||
|
private Logger log = new Logger(this);
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
@Nullable
|
||||||
|
private FUAuthenticatorActivity mActivity;
|
||||||
|
|
||||||
|
UserLoginTask(String email, String password, String tokenType, @NotNull Context context) {
|
||||||
|
mEmail = email;
|
||||||
|
mPassword = password;
|
||||||
|
mTokenType = tokenType;
|
||||||
|
mVolleyLogin = new Login(context);
|
||||||
|
if (context instanceof FUAuthenticatorActivity)
|
||||||
|
mActivity = (FUAuthenticatorActivity) context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(Void... params) {
|
||||||
|
// TODO: attempt authentication against a network service.
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
AtomicReference<LoginToken> login = new AtomicReference<>();
|
||||||
|
mVolleyLogin.doLogin(mEmail, mPassword, success -> {
|
||||||
|
mVolleyLogin.testLoginToken(success, success1 -> {
|
||||||
|
login.set(success);
|
||||||
|
latch.countDown();
|
||||||
|
}, error -> latch.countDown());
|
||||||
|
}, error -> latch.countDown());
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.d(login.get());
|
||||||
|
|
||||||
|
if (login.get() == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return login.get().toJsonString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*for (String credential : DUMMY_CREDENTIALS) {
|
||||||
|
String[] pieces = credential.split(":");
|
||||||
|
if (pieces[0].equals(mEmail)) {
|
||||||
|
// Account exists, return true if the password matches.
|
||||||
|
return pieces[1].equals(mPassword) ? "auth token here" : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: register the new account here.
|
||||||
|
return null;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(final String success) {
|
||||||
|
if (mActivity == null || mActivity.isFinishing())
|
||||||
|
return;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && mActivity.isDestroyed())
|
||||||
|
return;
|
||||||
|
mActivity.mAuthTask = null;
|
||||||
|
mActivity.showProgress(false);
|
||||||
|
|
||||||
|
if (success != null) {
|
||||||
|
final Intent res = new Intent();
|
||||||
|
res.putExtra(AccountManager.KEY_ACCOUNT_NAME, mEmail);
|
||||||
|
res.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountGeneral.ACCOUNT_TYPE);
|
||||||
|
res.putExtra(AccountManager.KEY_AUTHTOKEN, success);
|
||||||
|
res.putExtra(PARAM_USER_PASS, mPassword);
|
||||||
|
mActivity.finishLogin(res);
|
||||||
|
} else {
|
||||||
|
mActivity.mPasswordView.setError(mActivity.getString(R.string.error_incorrect_password));
|
||||||
|
mActivity.mPasswordView.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCancelled() {
|
||||||
|
if (mActivity == null || mActivity.isFinishing())
|
||||||
|
return;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && mActivity.isDestroyed())
|
||||||
|
return;
|
||||||
|
mActivity.mAuthTask = null;
|
||||||
|
mActivity.showProgress(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
package de.sebse.fuplanner.services.newkvv.network;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import de.sebse.fuplanner.services.KVV.types.LoginToken;
|
||||||
|
import de.sebse.fuplanner.tools.network.HTTPService;
|
||||||
|
import de.sebse.fuplanner.tools.network.NetworkCallback;
|
||||||
|
import de.sebse.fuplanner.tools.network.NetworkError;
|
||||||
|
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;
|
||||||
|
|
||||||
|
public class Login extends HTTPService {
|
||||||
|
public Login(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void testLoginToken(@NotNull LoginToken token, @NotNull NetworkCallback<LoginToken> callback, @NotNull NetworkErrorCallback errorCallback) {
|
||||||
|
get(String.format("https://kvv.imp.fu-berlin.de/direct/profile/%s.json", token.getUsername()), token.getCookies(), response -> {
|
||||||
|
String body = response.getParsed();
|
||||||
|
if (body == null) {
|
||||||
|
errorCallback.onError(new NetworkError(100172, 403, "Testing login failed!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject(body);
|
||||||
|
String displayName = json.getString("displayName");
|
||||||
|
String email = json.getString("email");
|
||||||
|
token.setAdditionals(displayName, email);
|
||||||
|
callback.onResponse(token);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
errorCallback.onError(new NetworkError(100171, 403, "Cannot parse profile!"));
|
||||||
|
}
|
||||||
|
}, error -> errorCallback.onError(new NetworkError(100170, error.networkResponse.statusCode, "Testing login failed!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void doLogin(String username, String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback error) {
|
||||||
|
startKVVSession(success -> {
|
||||||
|
String kvvJSESSIONID = success.get("JSESSIONID");
|
||||||
|
getSAMLRequest(kvvJSESSIONID, success1 -> startIdentSession(success1.get("Location"), success11 -> {
|
||||||
|
String identJSESSIONID = success11.get("JSESSIONID");
|
||||||
|
String ident_idp_authn_lc_key = success11.get("_idp_authn_lc_key");
|
||||||
|
String identROUTEID = success11.get("ROUTEID");
|
||||||
|
loginIdent(true, username, password, identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, success111 -> loginIdent(false, username, password, identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, success11112 -> {
|
||||||
|
String ident_idp_session = success11112.get("_idp_session");
|
||||||
|
getSAMLResponse(identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, ident_idp_session, success1111 -> loginKVV(success1111.get("RelayState"), success1111.get("SAMLResponse"), kvvJSESSIONID, success111112 -> {
|
||||||
|
LoginToken token = new LoginToken(username, success111112.get("shibsessionKey"), success111112.get("shibsessionName"), kvvJSESSIONID);
|
||||||
|
finishKVVlogin(token, success11111 -> callback.onResponse(token), error);
|
||||||
|
}, error), error);
|
||||||
|
}, error), error);
|
||||||
|
}, error), error);
|
||||||
|
}, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GET https://kvv.imp.fu-berlin.de/portal/login
|
||||||
|
-> JSESSIONID 5c10406f-588c-4c16-96e9-c80d115417de.tomcat1
|
||||||
|
*/
|
||||||
|
private void startKVVSession(final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
||||||
|
get("https://kvv.imp.fu-berlin.de/portal/login", null, response -> {
|
||||||
|
String cookies = response.getHeaders().get("Set-Cookie");
|
||||||
|
if (cookies==null) {
|
||||||
|
errorCallback.onError(new NetworkError(100101, -1, "Error on starting KVV session!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HashMap<String, String> object;
|
||||||
|
try {
|
||||||
|
object = getCookie(cookies, new String[]{"JSESSIONID"});
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
errorCallback.onError(new NetworkError(100102, -1, "Error on starting KVV session!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback.onResponse(object);
|
||||||
|
}, error -> errorCallback.onError(new NetworkError(100100, error.networkResponse.statusCode, "Error on starting KVV session!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GET https://kvv.imp.fu-berlin.de/sakai-login-tool/container
|
||||||
|
<- JSESSIONID
|
||||||
|
-> (Location-Header) https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
|
||||||
|
?SAMLRequest=fZLLb.....Q8yre3X1IHwkJKE0Mnpy/V9TH4A
|
||||||
|
&RelayState=ss:mem:7ea01e29157b8bd906f7002176.....0d1a505f2c8bf
|
||||||
|
*/
|
||||||
|
private void getSAMLRequest(String JSESSIONID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
||||||
|
HashMap<String, String> cookies = new HashMap<>();
|
||||||
|
cookies.put("JSESSIONID", JSESSIONID);
|
||||||
|
get("https://kvv.imp.fu-berlin.de/sakai-login-tool/container", cookies, response -> {
|
||||||
|
String location = response.getHeaders().get("Location");
|
||||||
|
if (location==null) {
|
||||||
|
errorCallback.onError(new NetworkError(100111, -1, "Error on getting SAML request!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HashMap<String, String> object = new HashMap<>();
|
||||||
|
object.put("Location", location);
|
||||||
|
callback.onResponse(object);
|
||||||
|
}, error -> errorCallback.onError(new NetworkError(100110, error.networkResponse.statusCode, "Error on getting SAML request!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GET https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
|
||||||
|
?SAMLRequest=fZLLbsIwEEV/JfI+cWJAUIsgpbAoEi2IpF10UznxUKw6dupxaPn7hkdb2LD29bkzRzNGUeuGZ63fmjV8toA++K61QX58SEnrDLcCFXIjakDuK55njwvOopg3znpbWU2CDBGcV9ZMrcG2BpeD26kKnteLlGy9b5BT+rHbRapuok0bluC0MpEEmm9VWVoNfhshWnpgM7pa5gUJZt0wyogD9h+iJBiv/P6aomQTbtqSdhNtlIYzZg1SOag8zfMlCeazlLyNqpHsy1gO2V1fVsNBMuqJoUyAJaxXDUaiiyG2MDfohfEpYXEyDJM4ZKxgCe/FPI5fSbA6L36vjFTm/bal8hRC/lAUq/C02gs4PK7VBchkfHDNj8Xuwv5trPhVTiY3BeOf4DG96DmVNvypA89nK6tVtQ8yre3X1IHwkJKE0Mnpy/V9TH4A
|
||||||
|
&RelayState=ss:mem:7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
|
||||||
|
-> JSESSIONID C4B6A428BA1F50746235D03F5D107A57
|
||||||
|
-> _idp_authn_lc_key 57a6ae26067f374cc3d0ccfc47e27b04b47752d2a3d4eb2782af0d3994535395
|
||||||
|
-> ROUTEID .1
|
||||||
|
*/
|
||||||
|
private void startIdentSession(String url, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
||||||
|
get(url, null, response -> {
|
||||||
|
String cookies = response.getHeaders().get("Set-Cookie");
|
||||||
|
if (cookies==null) {
|
||||||
|
errorCallback.onError(new NetworkError(100121, -1, "Error on starting Ident session!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HashMap<String, String> object;
|
||||||
|
try {
|
||||||
|
object = getCookie(cookies, new String[]{"JSESSIONID", "_idp_authn_lc_key", "ROUTEID"});
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
errorCallback.onError(new NetworkError(100122, -1, "Error on starting Ident session!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback.onResponse(object);
|
||||||
|
}, error -> errorCallback.onError(new NetworkError(100120, error.networkResponse.statusCode, "Error on starting Ident session!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
POST https://identity.fu-berlin.de/idp-fub/Authn/UserPassword
|
||||||
|
<- j_username seedorf96
|
||||||
|
<- j_password neinhieristpatrick
|
||||||
|
<- (Header-"Content-Type") application/x-www-form-urlencoded
|
||||||
|
<- JSESSIONID
|
||||||
|
<- _idp_authn_lc_key
|
||||||
|
<- ROUTEID
|
||||||
|
-> _idp_session OTMuMTkzLjg1LjMz|LQ==|OGYxOWI4MjA2NTQ4YWUwYzJkOWM4Mjk4YzcwZDMwZmJiZjBmMTdmMzkyZGU2OWIwY2JkNmZlNjlmNTRmNzBlMQ==|wLlzQal7VqyntmG2vLNn06wt8wQ=
|
||||||
|
*/
|
||||||
|
private void loginIdent(final boolean first, String username, String password, String JSESSIONID, String _idp_authn_lc_key, String ROUTEID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
||||||
|
HashMap<String, String> cookies = new HashMap<>();
|
||||||
|
cookies.put("JSESSIONID", JSESSIONID);
|
||||||
|
cookies.put("_idp_authn_lc_key", _idp_authn_lc_key);
|
||||||
|
cookies.put("ROUTEID", ROUTEID);
|
||||||
|
HashMap<String, String> body = new HashMap<>();
|
||||||
|
body.put("j_username", username);
|
||||||
|
body.put("j_password", password);
|
||||||
|
post("https://identity.fu-berlin.de/idp-fub/Authn/UserPassword", cookies, body, response -> {
|
||||||
|
if (first) {
|
||||||
|
callback.onResponse(new HashMap<>());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String cookies1 = response.getHeaders().get("Set-Cookie");
|
||||||
|
if (cookies1 ==null) {
|
||||||
|
errorCallback.onError(new NetworkError(100131, -1, "Error on logging in to Identity Server!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HashMap<String, String> object;
|
||||||
|
try {
|
||||||
|
object = getCookie(cookies1, new String[]{"_idp_session"});
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
errorCallback.onError(new NetworkError(100132, -1, "Error on logging in to Identity Server!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback.onResponse(object);
|
||||||
|
}, error -> errorCallback.onError(new NetworkError(100130, error.networkResponse.statusCode, "Error on logging in to Identity Server!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GET https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
|
||||||
|
<- JSESSIONID
|
||||||
|
<- _idp_authn_lc_key
|
||||||
|
<- ROUTEID
|
||||||
|
<- _idp_session
|
||||||
|
-> (BODY) RelayState 7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
|
||||||
|
-> (BODY) SAMLResponse PD94bWwgdmVyc2lvbj0...........wvc2FtbDJwOlJlc3BvbnNlPg==
|
||||||
|
*/
|
||||||
|
private void getSAMLResponse(String JSESSIONID, String _idp_authn_lc_key, String ROUTEID, String _idp_session, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
||||||
|
HashMap<String, String> cookies = new HashMap<>();
|
||||||
|
cookies.put("JSESSIONID", JSESSIONID);
|
||||||
|
cookies.put("_idp_authn_lc_key", _idp_authn_lc_key);
|
||||||
|
cookies.put("ROUTEID", ROUTEID);
|
||||||
|
cookies.put("_idp_session", _idp_session);
|
||||||
|
get("https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO", cookies, response -> {
|
||||||
|
String body = response.getParsed();
|
||||||
|
if (body == null) {
|
||||||
|
errorCallback.onError(new NetworkError(100143, -1, "Error on getting SAML response!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap<String, String> object = new HashMap<>();
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile("ss:mem:([0-9a-f]+)");
|
||||||
|
Matcher matcher = pattern.matcher(body);
|
||||||
|
if (!matcher.find()) {
|
||||||
|
errorCallback.onError(new NetworkError(100142, -1, "Error on getting SAML response!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
object.put("RelayState", "ss:mem:"+matcher.group(1));
|
||||||
|
|
||||||
|
pattern = Pattern.compile("name=\"SAMLResponse\" value=\"([0-9a-zA-Z+]+=*)");
|
||||||
|
matcher = pattern.matcher(body);
|
||||||
|
if (!matcher.find()) {
|
||||||
|
errorCallback.onError(new NetworkError(100141, -1, "Error on getting SAML response!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
object.put("SAMLResponse", matcher.group(1));
|
||||||
|
|
||||||
|
callback.onResponse(object);
|
||||||
|
}, error -> errorCallback.onError(new NetworkError(100140, error.networkResponse.statusCode, "Error on getting SAML response!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
POST https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST
|
||||||
|
<- RelayState 7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
|
||||||
|
<- SAMLResponse PD94bWwgdmVyc2lvbj0...........wvc2FtbDJwOlJlc3BvbnNlPg==
|
||||||
|
<- JSESSIONID
|
||||||
|
-> _shibsession_64656661756c7468747470733a2f2f6b76762e696d702e66752d6265726c696e2e64652f73686962626f6c657468
|
||||||
|
_b1912c5a03d733a80bd3fee772bf68d4
|
||||||
|
*/
|
||||||
|
private void loginKVV(String RelayState, String SAMLResponse, String JSESSIONID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
||||||
|
HashMap<String, String> cookies = new HashMap<>();
|
||||||
|
cookies.put("JSESSIONID", JSESSIONID);
|
||||||
|
HashMap<String, String> body = new HashMap<>();
|
||||||
|
body.put("RelayState", RelayState);
|
||||||
|
body.put("SAMLResponse", SAMLResponse);
|
||||||
|
post("https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST", cookies, body, response -> {
|
||||||
|
String cookies1 = response.getHeaders().get("Set-Cookie");
|
||||||
|
if (cookies1 ==null) {
|
||||||
|
errorCallback.onError(new NetworkError(100151, -1, "Error on starting KVV session!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HashMap<String, String> object = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile("(_shibsession_[0-9a-f]+)=([^;]+);");
|
||||||
|
Matcher matcher = pattern.matcher(cookies1);
|
||||||
|
if (!matcher.find()) {
|
||||||
|
errorCallback.onError(new NetworkError(100152, -1, "Error on starting Ident session!"));
|
||||||
|
}
|
||||||
|
object.put("shibsessionKey", matcher.group(1));
|
||||||
|
object.put("shibsessionName", matcher.group(2));
|
||||||
|
|
||||||
|
callback.onResponse(object);
|
||||||
|
}, error -> errorCallback.onError(new NetworkError(100150, error.networkResponse.statusCode, "Error on starting Ident session!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
GET https://kvv.imp.fu-berlin.de/sakai-login-tool/container
|
||||||
|
<- JSESSIONID
|
||||||
|
<- _shibsession_64656661756c7468747470733a2f2f6b76762e696d702e66752d6265726c696e2e64652f73686962626f6c657468
|
||||||
|
_b1912c5a03d733a80bd3fee772bf68d4
|
||||||
|
*/
|
||||||
|
private void finishKVVlogin(LoginToken loginToken, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
|
||||||
|
get("https://kvv.imp.fu-berlin.de/sakai-login-tool/container", loginToken.getCookies(), response -> callback.onResponse(new HashMap<>()), error -> errorCallback.onError(new NetworkError(100160, error.networkResponse.statusCode, "Cannot finish login process!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private String getCookie(String cookies, String name) throws NoSuchFieldException {
|
||||||
|
Pattern pattern = Pattern.compile(name+"=([^;]+);");
|
||||||
|
Matcher matcher = pattern.matcher(cookies);
|
||||||
|
if (!matcher.find()) {
|
||||||
|
log.e("GETcookie failed", name);
|
||||||
|
log.e("GETcookie failed", cookies);
|
||||||
|
throw new NoSuchFieldException();
|
||||||
|
}
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap<String, String> getCookie(String cookies, String[] names) throws NoSuchFieldException {
|
||||||
|
HashMap<String, String> result = new HashMap<>();
|
||||||
|
for (String name: names) {
|
||||||
|
result.put(name,this.getCookie(cookies, name));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package de.sebse.fuplanner.tools;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.accounts.AccountManager;
|
||||||
|
import android.accounts.AuthenticatorException;
|
||||||
|
import android.accounts.OperationCanceledException;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import de.sebse.fuplanner.MainActivity;
|
||||||
|
import de.sebse.fuplanner.services.KVV.Login;
|
||||||
|
import de.sebse.fuplanner.tools.logging.Logger;
|
||||||
|
|
||||||
|
public class CustomAccountManager {
|
||||||
|
private final AccountManager mAccountManager;
|
||||||
|
private final ActivityInterface mActivityInterface;
|
||||||
|
private Logger log = new Logger(this);
|
||||||
|
|
||||||
|
public CustomAccountManager(AccountManager manager, ActivityInterface activityInterface) {
|
||||||
|
mAccountManager = manager;
|
||||||
|
this.mActivityInterface = activityInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void doInvalidateTokenSync(String accountType, String authTokenType) {
|
||||||
|
Account account = mAccountManager.getAccountsByType(accountType)[0];
|
||||||
|
try {
|
||||||
|
String token = mAccountManager.blockingGetAuthToken(account, authTokenType, true);
|
||||||
|
mAccountManager.invalidateAuthToken(accountType, token);
|
||||||
|
} catch (AuthenticatorException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (OperationCanceledException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doInvalidateToken(String accountType, String authTokenType, Login.BooleanInterface callback) {
|
||||||
|
Account account = mAccountManager.getAccountsByType(accountType)[0];
|
||||||
|
mAccountManager.getAuthToken(account, authTokenType, null, true, accountManagerFuture -> {
|
||||||
|
try {
|
||||||
|
Bundle bnd = accountManagerFuture.getResult();
|
||||||
|
String token = bnd.getString(AccountManager.KEY_AUTHTOKEN);
|
||||||
|
mAccountManager.invalidateAuthToken(accountType, token);
|
||||||
|
if (callback != null)
|
||||||
|
callback.run(true);
|
||||||
|
return;
|
||||||
|
} catch (AuthenticatorException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (OperationCanceledException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (callback != null)
|
||||||
|
callback.run(false);
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteAccount(String accountType) {
|
||||||
|
Account[] accounts = mAccountManager.getAccountsByType(accountType);
|
||||||
|
int[] count = {accounts.length};
|
||||||
|
for (Account account: accounts) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||||
|
mAccountManager.removeAccount(account, null, null, null);
|
||||||
|
} else {
|
||||||
|
mAccountManager.removeAccount(account, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getTokenByType(String accountType, String authTokenType, @Nullable StringInterface callback) {
|
||||||
|
//Account account = mAccountManager.getAccountsByType(accountType)[0];
|
||||||
|
mAccountManager.getAuthTokenByFeatures(accountType, authTokenType, null, mActivityInterface.get(), null, null, accountManagerFuture -> {
|
||||||
|
try {
|
||||||
|
Bundle bnd = accountManagerFuture.getResult();
|
||||||
|
final String authtoken = bnd.getString(AccountManager.KEY_AUTHTOKEN);
|
||||||
|
if (callback != null)
|
||||||
|
callback.run(authtoken);
|
||||||
|
return;
|
||||||
|
} catch (AuthenticatorException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (OperationCanceledException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (callback != null)
|
||||||
|
callback.run(null);
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTokenByTypeSync(String accountType, String authTokenType) {
|
||||||
|
Account account = mAccountManager.getAccountsByType(accountType)[0];
|
||||||
|
MainActivity activity = this.mActivityInterface.get();
|
||||||
|
if (activity != null) {
|
||||||
|
try {
|
||||||
|
return mAccountManager.blockingGetAuthToken(account, authTokenType, true);
|
||||||
|
} catch (AuthenticatorException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (OperationCanceledException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAccounts(String accountType) {
|
||||||
|
return mAccountManager.getAccountsByType(accountType).length != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ActivityInterface {
|
||||||
|
@Nullable
|
||||||
|
MainActivity get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface StringInterface {
|
||||||
|
void run(@Nullable String string);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ public class HTTPService {
|
|||||||
private final EventListener<VolleyError> errorResponseListener = new EventListener<>();
|
private final EventListener<VolleyError> errorResponseListener = new EventListener<>();
|
||||||
private final EventListener<Result> successResponseListener = new EventListener<>();
|
private final EventListener<Result> successResponseListener = new EventListener<>();
|
||||||
|
|
||||||
protected HTTPService(Context context) {
|
public HTTPService(Context context) {
|
||||||
this.mContext = context;
|
this.mContext = context;
|
||||||
requestQueue = Volley.newRequestQueue(context, new BetterHurlStack(false));
|
requestQueue = Volley.newRequestQueue(context, new BetterHurlStack(false));
|
||||||
}
|
}
|
||||||
|
|||||||
97
app/src/main/res/layout/activity_fu_authenticator.xml
Normal file
97
app/src/main/res/layout/activity_fu_authenticator.xml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
tools:context=".services.newkvv.FUAuthenticatorActivity">
|
||||||
|
|
||||||
|
<!-- Login progress -->
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/login_progress"
|
||||||
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:id="@+id/login_form"
|
||||||
|
tools:context="de.sebse.fuplanner.services.newkvv.FUAuthenticatorActivity">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="56dp"
|
||||||
|
android:paddingLeft="24dp"
|
||||||
|
android:paddingRight="24dp">
|
||||||
|
|
||||||
|
<ImageView android:src="@mipmap/ic_launcher"
|
||||||
|
android:layout_width="128dp"
|
||||||
|
android:layout_height="128dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:contentDescription="@string/cd_ic_launcher"/>
|
||||||
|
|
||||||
|
<!-- Email Label -->
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
<EditText android:id="@+id/input_username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textEmailAddress"
|
||||||
|
android:hint="@string/username"
|
||||||
|
android:autofillHints="username"
|
||||||
|
tools:targetApi="o" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<!-- Password Label -->
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
<EditText android:id="@+id/input_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:hint="@string/password"
|
||||||
|
android:autofillHints="password"
|
||||||
|
tools:targetApi="o"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/btn_login"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:text="@string/log_in"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/btn_offline"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
tools:context="de.sebse.fuplanner.fragments.LoginFragment">
|
tools:context="de.sebse.fuplanner.services.newkvv.FUAuthenticatorActivity">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
|||||||
@@ -90,4 +90,17 @@
|
|||||||
<string name="update_news">Update News</string>
|
<string name="update_news">Update News</string>
|
||||||
<string name="tricks">Tips/Tricks</string>
|
<string name="tricks">Tips/Tricks</string>
|
||||||
<string name="back_to_exit">Please click BACK again to exit!</string>
|
<string name="back_to_exit">Please click BACK again to exit!</string>
|
||||||
|
<string name="title_activity_fuauthenticator">Sign in</string>
|
||||||
|
<!-- Strings related to login -->
|
||||||
|
<string name="prompt_email">Email</string>
|
||||||
|
<string name="prompt_password">Password (optional)</string>
|
||||||
|
<string name="action_sign_in">Sign in or register</string>
|
||||||
|
<string name="action_sign_in_short">Sign in</string>
|
||||||
|
<string name="error_invalid_email">This email address is invalid</string>
|
||||||
|
<string name="error_invalid_password">This password is too short</string>
|
||||||
|
<string name="error_incorrect_password">This password is incorrect</string>
|
||||||
|
<string name="error_field_required">This field is required</string>
|
||||||
|
<string name="permission_rationale">"Contacts permissions are needed for providing email
|
||||||
|
completions."
|
||||||
|
</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
6
app/src/main/res/xml/authenticator.xml
Normal file
6
app/src/main/res/xml/authenticator.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:accountType="de.sebse.fuplanner.fuauth"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:smallIcon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"/>
|
||||||
Reference in New Issue
Block a user