Session cookie caught and saved to use in requests after successful SAML-based federated SSO

This commit is contained in:
David A. Velasco 2013-08-01 17:47:09 +02:00
parent 5ede3e9909
commit 52bc433bb1
7 changed files with 142 additions and 39 deletions

View file

@ -259,7 +259,8 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator {
if (!authTokenType.equals(AUTH_TOKEN_TYPE) &&
!authTokenType.equals(AUTH_TOKEN_TYPE_PASSWORD) &&
!authTokenType.equals(AUTH_TOKEN_TYPE_ACCESS_TOKEN) &&
!authTokenType.equals(AUTH_TOKEN_TYPE_REFRESH_TOKEN) ) {
!authTokenType.equals(AUTH_TOKEN_TYPE_REFRESH_TOKEN) &&
!authTokenType.equals(AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE)) {
throw new UnsupportedAuthTokenTypeException();
}
}

View file

@ -22,6 +22,7 @@ import com.owncloud.android.Log_OC;
import com.owncloud.android.ui.dialog.SslValidatorDialog;
import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;
import com.owncloud.android.utils.OwnCloudVersion;
import com.owncloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener;
import com.owncloud.android.network.OwnCloudClientUtils;
import com.owncloud.android.operations.OwnCloudServerCheckOperation;
import com.owncloud.android.operations.ExistenceCheckOperation;
@ -79,7 +80,7 @@ import eu.alefzero.webdav.WebdavClient;
* @author David A. Velasco
*/
public class AuthenticatorActivity extends AccountAuthenticatorActivity
implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeListener, OnEditorActionListener {
implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeListener, OnEditorActionListener, SsoWebViewClientListener {
private static final String TAG = AuthenticatorActivity.class.getSimpleName();
@ -146,7 +147,6 @@ implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeList
private EditText mPasswordInput;
private CheckBox mOAuth2Check;
private String mOAuthAccessToken;
private TextView mOAuthAuthEndpointText;
private TextView mOAuthTokenEndpointText;
@ -156,6 +156,8 @@ implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeList
private SsoWebViewClient mWebViewClient;
private View mOkButton;
private String mAuthToken;
/**
@ -337,7 +339,7 @@ implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeList
cookieManager.setAcceptCookie(true);
//cookieManager.removeSessionCookie();
mWebViewClient = new SsoWebViewClient(this);
mWebViewClient = new SsoWebViewClient(mHandler, this);
mSsoWebView.setWebViewClient(mWebViewClient);
WebSettings webSettings = mSsoWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
@ -1026,11 +1028,11 @@ implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeList
showDialog(DIALOG_LOGIN_PROGRESS);
/// time to test the retrieved access token on the ownCloud server
mOAuthAccessToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Constants.KEY_ACCESS_TOKEN);
Log_OC.d(TAG, "Got ACCESS TOKEN: " + mOAuthAccessToken);
mAuthToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Constants.KEY_ACCESS_TOKEN);
Log_OC.d(TAG, "Got ACCESS TOKEN: " + mAuthToken);
mAuthCheckOperation = new ExistenceCheckOperation("", this, false);
WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(mHostBaseUrl + webdav_path), this, true);
client.setBearerCredentials(mOAuthAccessToken);
client.setBearerCredentials(mAuthToken);
mAuthCheckOperation.execute(client, this, mHandler);
} else {
@ -1110,11 +1112,17 @@ implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeList
Bundle response = new Bundle();
response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type);
boolean isOAuth = mOAuth2Check.isChecked();
if (isOAuth) {
response.putString(AccountManager.KEY_AUTHTOKEN, mOAuthAccessToken);
if (AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN.equals(mCurrentAuthTokenType)) {
response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken);
// the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention
mAccountMgr.setAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, mOAuthAccessToken);
mAccountMgr.setAuthToken(mAccount, mCurrentAuthTokenType, mAuthToken);
} else if (AccountAuthenticator.AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE.equals(mCurrentAuthTokenType)) {
response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken);
// the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention
mAccountMgr.setAuthToken(mAccount, mCurrentAuthTokenType, mAuthToken);
} else {
response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString());
mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString());
@ -1132,11 +1140,15 @@ implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeList
*/
private void createAccount() {
/// create and save new ownCloud account
boolean isOAuth = mOAuth2Check.isChecked();
boolean isOAuth = AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN.equals(mCurrentAuthTokenType);
boolean isSaml = AccountAuthenticator.AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE.equals(mCurrentAuthTokenType);
Uri uri = Uri.parse(mHostBaseUrl);
String username = mUsernameInput.getText().toString().trim();
if (isOAuth) {
if (isSaml) {
username = mAccountNameInput.getText().toString().trim();
} else if (isOAuth) {
username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong();
}
String accountName = username + "@" + uri.getHost();
@ -1144,8 +1156,8 @@ implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeList
accountName += ":" + uri.getPort();
}
mAccount = new Account(accountName, AccountAuthenticator.ACCOUNT_TYPE);
if (isOAuth) {
mAccountMgr.addAccountExplicitly(mAccount, "", null); // with our implementation, the password is never input in the app
if (isOAuth || isSaml) {
mAccountMgr.addAccountExplicitly(mAccount, "", null); // with external authorizations, the password is never input in the app
} else {
mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null);
}
@ -1164,17 +1176,20 @@ implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeList
final Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountAuthenticator.ACCOUNT_TYPE);
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name);
if (!isOAuth)
intent.putExtra(AccountManager.KEY_AUTHTOKEN, AccountAuthenticator.ACCOUNT_TYPE); // TODO check this; not sure it's right; maybe
/*if (!isOAuth)
intent.putExtra(AccountManager.KEY_AUTHTOKEN, AccountAuthenticator.ACCOUNT_TYPE); */
intent.putExtra(AccountManager.KEY_USERDATA, username);
if (isOAuth) {
mAccountMgr.setAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, mOAuthAccessToken);
if (isOAuth || isSaml) {
mAccountMgr.setAuthToken(mAccount, mCurrentAuthTokenType, mAuthToken);
}
/// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA
mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, mDiscoveredVersion.toString());
mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL, mHostBaseUrl);
if (isOAuth)
mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE"); // TODO this flag should be unnecessary
if (isSaml) {
mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE");
} else if (isOAuth) {
mAccountMgr.setUserData(mAccount, AccountAuthenticator.KEY_SUPPORTS_OAUTH2, "TRUE");
}
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
@ -1482,4 +1497,27 @@ implements OnRemoteOperationListener, OnSslValidatorListener, OnFocusChangeList
public abstract boolean onDrawableTouch(final MotionEvent event);
}
@Override
public void onSsoFinished(String sessionCookie) {
//Toast.makeText(this, "got cookies: " + sessionCookie, Toast.LENGTH_LONG).show();
if (sessionCookie != null && sessionCookie.length() > 0) {
Log_OC.d(TAG, "Successful SSO - time to save the account");
mAuthToken = sessionCookie;
if (mAction == ACTION_CREATE) {
createAccount();
} else {
updateToken();
}
finish();
} else {
// TODO - show fail
Log_OC.d(TAG, "SSO failed");
}
}
}

View file

@ -17,13 +17,14 @@
package com.owncloud.android.authentication;
import android.content.Context;
import java.lang.ref.WeakReference;
import android.graphics.Bitmap;
import android.os.Handler;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import com.owncloud.android.Log_OC;
@ -40,11 +41,17 @@ public class SsoWebViewClient extends WebViewClient {
private static final String TAG = SsoWebViewClient.class.getSimpleName();
private Context mContext;
public interface SsoWebViewClientListener {
public void onSsoFinished(String sessionCookie);
}
private Handler mListenerHandler;
private WeakReference<SsoWebViewClientListener> mListenerRef;
private String mTargetUrl;
public SsoWebViewClient (Context context) {
mContext = context;
public SsoWebViewClient (Handler listenerHandler, SsoWebViewClientListener listener) {
mListenerHandler = listenerHandler;
mListenerRef = new WeakReference<SsoWebViewClient.SsoWebViewClientListener>(listener);
mTargetUrl = "fake://url.to.be.set";
}
@ -62,8 +69,19 @@ public class SsoWebViewClient extends WebViewClient {
if (url.startsWith(mTargetUrl)) {
view.setVisibility(View.GONE);
CookieManager cookieManager = CookieManager.getInstance();
String cookies = cookieManager.getCookie(url);
Toast.makeText(mContext, "got cookies: " + cookies, Toast.LENGTH_LONG).show();
final String cookies = cookieManager.getCookie(url);
if (mListenerHandler != null && mListenerRef != null) {
// this is good idea because onPageStarted is not running in the UI thread
mListenerHandler.post(new Runnable() {
@Override
public void run() {
SsoWebViewClientListener listener = mListenerRef.get();
if (listener != null) {
listener.onSsoFinished(cookies);
}
}
});
}
}
}

View file

@ -96,6 +96,10 @@ public class OwnCloudClientUtils {
String accessToken = am.blockingGetAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, false);
client.setBearerCredentials(accessToken); // TODO not assume that the access token is a bearer token
} else if (am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO) != null) { // TODO avoid a call to getUserData here
String accessToken = am.blockingGetAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE, false);
client.setSsoSessionCookie(accessToken);
} else {
String username = account.name.substring(0, account.name.lastIndexOf('@'));
//String password = am.getPassword(account);
@ -115,10 +119,16 @@ public class OwnCloudClientUtils {
AccountManagerFuture<Bundle> future = am.getAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, null, currentActivity, null, null);
Bundle result = future.getResult();
String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN);
//String accessToken = am.blockingGetAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_ACCESS_TOKEN, false);
if (accessToken == null) throw new AuthenticatorException("WTF!");
client.setBearerCredentials(accessToken); // TODO not assume that the access token is a bearer token
} else if (am.getUserData(account, AccountAuthenticator.KEY_SUPPORTS_SAML_WEB_SSO) != null) { // TODO avoid a call to getUserData here
AccountManagerFuture<Bundle> future = am.getAuthToken(account, AccountAuthenticator.AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE, null, currentActivity, null, null);
Bundle result = future.getResult();
String accessToken = result.getString(AccountManager.KEY_AUTHTOKEN);
if (accessToken == null) throw new AuthenticatorException("WTF!");
client.setSsoSessionCookie(accessToken);
} else {
String username = account.name.substring(0, account.name.lastIndexOf('@'));
//String password = am.getPassword(account);

View file

@ -126,7 +126,7 @@ public class RemoteOperationResult implements Serializable {
break;
default:
mCode = ResultCode.UNHANDLED_HTTP_CODE;
Log_OC.d(TAG, "RemoteOperationResult has prcessed UNHANDLED_HTTP_CODE: " + httpCode);
Log_OC.d(TAG, "RemoteOperationResult has processed UNHANDLED_HTTP_CODE: " + httpCode);
}
}
}

View file

@ -208,9 +208,9 @@ public class SynchronizeFolderOperation extends RemoteOperation {
} else {
mFailsInFavouritesFound++;
if (contentsResult.getException() != null) {
Log_OC.d(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage(), contentsResult.getException());
Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage(), contentsResult.getException());
} else {
Log_OC.d(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage());
Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage());
}
}
} // won't let these fails break the synchronization process
@ -247,16 +247,25 @@ public class SynchronizeFolderOperation extends RemoteOperation {
} else {
result = new RemoteOperationResult(false, status);
}
Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage());
} catch (Exception e) {
result = new RemoteOperationResult(e);
Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage(), result.getException());
} finally {
if (query != null)
query.releaseConnection(); // let the connection available for other methods
if (result.isSuccess()) {
Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage());
} else {
if (result.isException()) {
Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage(), result.getException());
} else {
Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage());
}
}
}
return result;

View file

@ -27,11 +27,13 @@ import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpVersion;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthPolicy;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.http.HttpStatus;
@ -48,6 +50,7 @@ public class WebdavClient extends HttpClient {
private Uri mUri;
private Credentials mCredentials;
private boolean mFollowRedirects;
private String mSsoSessionCookie;
final private static String TAG = "WebdavClient";
public static final String USER_AGENT = "Android-ownCloud";
@ -62,6 +65,7 @@ public class WebdavClient extends HttpClient {
getParams().setParameter(HttpMethodParams.USER_AGENT, USER_AGENT);
getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
mFollowRedirects = true;
mSsoSessionCookie = null;
}
public void setBearerCredentials(String accessToken) {
@ -73,6 +77,7 @@ public class WebdavClient extends HttpClient {
mCredentials = new BearerCredentials(accessToken);
getState().setCredentials(AuthScope.ANY, mCredentials);
mSsoSessionCookie = null;
}
public void setBasicCredentials(String username, String password) {
@ -83,8 +88,17 @@ public class WebdavClient extends HttpClient {
getParams().setAuthenticationPreemptive(true);
mCredentials = new UsernamePasswordCredentials(username, password);
getState().setCredentials(AuthScope.ANY, mCredentials);
mSsoSessionCookie = null;
}
public void setSsoSessionCookie(String accessToken) {
getParams().setAuthenticationPreemptive(false);
getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
mSsoSessionCookie = accessToken;
mCredentials = null;
}
/**
* Check if a file exists in the OC server
*
@ -96,7 +110,6 @@ public class WebdavClient extends HttpClient {
public boolean existsFile(String path) throws IOException, HttpException {
HeadMethod head = new HeadMethod(mUri.toString() + WebdavUtils.encodePath(path));
try {
head.setFollowRedirects(mFollowRedirects);
int status = executeMethod(head);
Log_OC.d(TAG, "HEAD to " + path + " finished with HTTP status " + status + ((status != HttpStatus.SC_OK)?"(FAIL)":""));
exhaustResponse(head.getResponseBodyAsStream());
@ -131,13 +144,27 @@ public class WebdavClient extends HttpClient {
if (connectionTimeout >= 0) {
getHttpConnectionManager().getParams().setConnectionTimeout(connectionTimeout);
}
method.setFollowRedirects(mFollowRedirects);
return executeMethod(method);
} finally {
getParams().setSoTimeout(oldSoTimeout);
getHttpConnectionManager().getParams().setConnectionTimeout(oldConnectionTimeout);
}
}
@Override
public int executeMethod(HttpMethod method) throws IOException, HttpException {
try {
method.setFollowRedirects(mFollowRedirects);
} catch (Exception e) {
}
if (mSsoSessionCookie != null && mSsoSessionCookie.length() > 0) {
method.setRequestHeader("Cookie", mSsoSessionCookie);
}
return super.executeMethod(method);
}
/**
* Exhausts a not interesting HTTP response. Encouraged by HttpClient documentation.
@ -182,6 +209,6 @@ public class WebdavClient extends HttpClient {
public void setFollowRedirects(boolean followRedirects) {
mFollowRedirects = followRedirects;
}
}
}