package org.ovirt.mobile.movirt.auth; import android.accounts.AbstractAccountAuthenticator; import android.accounts.Account; import android.accounts.AccountAuthenticatorResponse; import android.accounts.AccountManager; import android.accounts.NetworkErrorException; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.text.TextUtils; import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.SystemService; import org.ovirt.mobile.movirt.Constants; import org.ovirt.mobile.movirt.auth.properties.AccountProperty; import org.ovirt.mobile.movirt.auth.properties.PropertyUtils; import org.ovirt.mobile.movirt.auth.properties.property.Cert; import org.ovirt.mobile.movirt.auth.properties.property.CertHandlingStrategy; import org.ovirt.mobile.movirt.auth.properties.property.version.Version; import org.ovirt.mobile.movirt.provider.OVirtContract; import org.ovirt.mobile.movirt.rest.client.LoginClient; import org.ovirt.mobile.movirt.ui.auth.AuthenticatorActivity; import org.ovirt.mobile.movirt.ui.auth.AuthenticatorActivity_; import org.ovirt.mobile.movirt.util.JsonUtils; import org.ovirt.mobile.movirt.util.message.MessageHelper; import org.ovirt.mobile.movirt.util.preferences.SharedPreferencesHelper; @EBean(scope = EBean.Scope.Singleton) public class MovirtAuthenticator extends AbstractAccountAuthenticator { private static final String ACCOUNT_TYPE = Constants.APP_PACKAGE_DOT + "authenticator"; private static final Account MOVIRT_ACCOUNT = new Account("oVirt", MovirtAuthenticator.ACCOUNT_TYPE); @Bean LoginClient loginClient; @SystemService AccountManager accountManager; @Bean MessageHelper messageHelper; @Bean SharedPreferencesHelper sharedPreferencesHelper; private Context context; public MovirtAuthenticator(Context context) { super(context); this.context = context; } @Override public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse, String s) { return null; } @Override public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse, String s, String s2, String[] strings, Bundle options) throws NetworkErrorException { if (getResource(AccountProperty.ACCOUNT_CONFIGURED, Boolean.class)) { messageHelper.showToast("Only one moVirt account is allowed per device"); return null; } else { return createAccountActivity(accountAuthenticatorResponse); } } @Override public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, Bundle bundle) throws NetworkErrorException { return null; } @Override public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String authTokenType, Bundle bundle) throws NetworkErrorException { String authToken = accountManager.peekAuthToken(account, authTokenType); if (TextUtils.isEmpty(authToken)) { final String username = getResource(AccountProperty.USERNAME, String.class); final String password = getResource(AccountProperty.PASSWORD, String.class); if (username != null && password != null) { try { if (!AuthenticatorActivity.isInUserLogin()) { // do not attempt to login while user tries authToken = loginClient.login(username, password); } } catch (Exception x) { // do not fail on bad login info } if (!TextUtils.isEmpty(authToken)) { accountManager.setAuthToken(account, authTokenType, authToken); } } } 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; } return createAccountActivity(accountAuthenticatorResponse); } private Bundle createAccountActivity(AccountAuthenticatorResponse accountAuthenticatorResponse) { final Intent intent = new Intent(context, AuthenticatorActivity_.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, accountAuthenticatorResponse); 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; } public boolean initAccount(String password) { boolean initialized = accountManager.addAccountExplicitly(getAccount(), password, Bundle.EMPTY); if (initialized) { ContentResolver.setIsSyncable(getAccount(), OVirtContract.CONTENT_AUTHORITY, 1); ContentResolver.setSyncAutomatically(getAccount(), OVirtContract.CONTENT_AUTHORITY, true); sharedPreferencesHelper.updatePeriodicSync(); } return initialized; } public Account getAccount() { return MOVIRT_ACCOUNT; } /** * @throws IllegalArgumentException if property is not settable */ public void setResource(AccountProperty property, Object object) { Account account = getAccount(); switch (property) { case AUTH_TOKEN: if (object == null) { accountManager.invalidateAuthToken(property.getPackageKey(), accountManager.peekAuthToken(account, property.getPackageKey())); } accountManager.setAuthToken(account, property.getPackageKey(), PropertyUtils.convertToString(object)); break; case PEEK_AUTH_TOKEN: case FUTURE_AUTH_TOKEN: throw new IllegalArgumentException(property.name() + " cannot be set! Use AUTH_TOKEN."); case PASSWORD: accountManager.setPassword(account, PropertyUtils.convertToString(object)); // triggers sync in later APIs (Android 6) break; case USERNAME: case PASSWORD_VISIBILITY: case API_URL: case VERSION: case CERT_HANDLING_STRATEGY: case HAS_ADMIN_PERMISSIONS: case CERTIFICATE_CHAIN: case VALID_HOSTNAME_LIST: case CUSTOM_CERTIFICATE_LOCATION: case FIRST_LOGIN: accountManager.setUserData(account, property.getPackageKey(), PropertyUtils.convertToString(object)); break; default: throw new IllegalArgumentException(property.name() + " cannot be set!"); } } @SuppressWarnings("unchecked") public <E> E getResource(AccountProperty property, Class<E> clazz) { return (E) getResource(property); } /** * Should not throw Exceptions */ public Object getResource(AccountProperty property) { Account account = getAccount(); switch (property) { case AUTH_TOKEN: // fallback to non blocking peek, used exclusively by AccountPropertiesManager.setAndNotify case PEEK_AUTH_TOKEN: return accountManager.peekAuthToken(account, AccountProperty.AUTH_TOKEN.getPackageKey()); case FUTURE_AUTH_TOKEN: return accountManager.getAuthToken(account, AccountProperty.AUTH_TOKEN.getPackageKey(), null, false, null, null); case ACCOUNT_CONFIGURED: return accountManager.getAccountsByType(ACCOUNT_TYPE).length > 0; case PASSWORD: return accountManager.getPassword(account); case USERNAME: case API_URL: return read(property); case API_BASE_URL: String baseUrl = read(AccountProperty.API_URL); return baseUrl == null ? null : baseUrl.replaceFirst("/api$", ""); case VERSION: return getApiVersion(property); case CERT_HANDLING_STRATEGY: return getCertHandlingStrategy(property); case PASSWORD_VISIBILITY: case HAS_ADMIN_PERMISSIONS: case CUSTOM_CERTIFICATE_LOCATION: return read(property, false); case FIRST_LOGIN: return read(property, true); case CERTIFICATE_CHAIN: return getCertificateChain(property); case VALID_HOSTNAMES: return PropertyUtils.catenateToCsv(getValidHostnames(AccountProperty.VALID_HOSTNAME_LIST)); case VALID_HOSTNAME_LIST: return getValidHostnames(property); default: return null; } } private Version getApiVersion(AccountProperty property) { Version result = readObject(property, Version.class); if (result == null) { result = new Version(); } return result; } private String[] getValidHostnames(AccountProperty property) { String[] result = readObject(property, String[].class); return (result == null) ? new String[]{} : result; } private Cert[] getCertificateChain(AccountProperty property) { Cert[] result = readObject(property, Cert[].class); return (result == null) ? new Cert[]{} : result; } private CertHandlingStrategy getCertHandlingStrategy(AccountProperty property) { String strategy = read(property); if (TextUtils.isEmpty(strategy)) { return CertHandlingStrategy.TRUST_SYSTEM; } try { return CertHandlingStrategy.from(Long.valueOf(strategy)); } catch (NumberFormatException e) { return CertHandlingStrategy.TRUST_SYSTEM; } } private <T> T readObject(AccountProperty property, Class<T> clazz) { try { return JsonUtils.stringToObject(read(property), clazz); } catch (Exception ignored) { return null; } } private Boolean read(AccountProperty property, boolean defRes) { String res = accountManager.getUserData(getAccount(), property.getPackageKey()); if (TextUtils.isEmpty(res)) { return defRes; } return Boolean.valueOf(res); } private String read(AccountProperty property, String defRes) { String res = accountManager.getUserData(getAccount(), property.getPackageKey()); if (TextUtils.isEmpty(res)) { return defRes; } return res; } private String read(AccountProperty property) { return accountManager.getUserData(getAccount(), property.getPackageKey()); } }