Implement OAuth flow

Closes #15
This commit is contained in:
Patrick Boos 2011-10-19 09:34:43 +09:00
parent c5556d1080
commit 57af1f552a
16 changed files with 196 additions and 75 deletions

View file

@ -7,5 +7,7 @@
<classpathentry kind="lib" path="libs/apache-mime4j-core-0.7.jar"/>
<classpathentry kind="lib" path="libs/commons-codec-1.4.jar"/>
<classpathentry kind="lib" path="libs/httpmime-4.1.2.jar"/>
<classpathentry kind="lib" path="libs/signpost-core-1.2.1.1.jar"/>
<classpathentry kind="lib" path="libs/signpost-commonshttp4-1.2.1.1.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

Binary file not shown.

Binary file not shown.

View file

@ -12,6 +12,12 @@
<string name="setting_account_loggedin_key">setting_loggedin</string>
<string name="setting_account_loggedin_logout">Log out</string>
<string name="setting_account_loggedin_login">Log in</string>
<string name="setting_account_loggedin_consumer_key">setting_loggedin</string>
<!-- OAuth store information -->
<string name="setting_oauth_consumer_key">setting_oauth_consumer_key</string>
<string name="setting_oauth_consumer_secret">setting_oauth_consumer_secret</string>
<string name="setting_oauth_token">setting_oauth_token</string>
<string name="setting_oauth_token_secret">setting_oauth_token_secret</string>
<string name="setting_account_edit_account_title">Edit account</string>
<string name="setting_account_change_profile_picture_title">Change profile picture</string>
@ -22,4 +28,5 @@
<string name="setting_autoupload_tag_key">setting_autoupload_tag</string>
<string name="setting_autoupload_tag_default">autoupload</string>
</resources>

View file

@ -9,7 +9,6 @@ import java.util.List;
import me.openphoto.android.app.model.Photo;
import me.openphoto.android.app.net.IOpenPhotoApi;
import me.openphoto.android.app.net.OpenPhotoApi;
import me.openphoto.android.app.net.Paging;
import me.openphoto.android.app.net.PhotosResponse;
import me.openphoto.android.app.net.ReturnSize;
@ -82,8 +81,7 @@ public class GalleryActivity extends Activity implements OnItemClickListener {
public PhotosEndlessAdapter(String tagFilter) {
super();
mOpenPhotoApi = OpenPhotoApi
.createInstance(Preferences.getServer(GalleryActivity.this));
mOpenPhotoApi = Preferences.getApi(GalleryActivity.this);
mTagFilter = new ArrayList<String>(1);
if (tagFilter != null) {
mTagFilter.add(tagFilter);

View file

@ -8,7 +8,6 @@ import java.net.URL;
import me.openphoto.android.app.model.Photo;
import me.openphoto.android.app.net.IOpenPhotoApi;
import me.openphoto.android.app.net.OpenPhotoApi;
import me.openphoto.android.app.net.Paging;
import me.openphoto.android.app.net.ReturnSize;
import me.openphoto.android.app.ui.widget.ActionBar;
@ -52,7 +51,7 @@ public class MainActivity extends Activity implements OnClickListener {
mActionBar = (ActionBar) findViewById(R.id.actionbar);
new LoadImageTask().execute();
// Get refereneces to navigation buttons
// Get references to navigation buttons
searchBtn = (ImageButton) findViewById(R.id.main_search_btn);
cameraBtn = (ImageButton) findViewById(R.id.main_camera_btn);
galleryBtn = (ImageButton) findViewById(R.id.main_gallery_btn);
@ -73,8 +72,7 @@ public class MainActivity extends Activity implements OnClickListener {
@Override
protected Bitmap doInBackground(Void... params) {
IOpenPhotoApi api = OpenPhotoApi
.createInstance(Preferences.getServer(MainActivity.this));
IOpenPhotoApi api = Preferences.getApi(MainActivity.this);
try {
Photo photo = api.getPhotos(new ReturnSize(600, 600), null, new Paging(1, 1))
.getPhotos().get(0);

View file

@ -1,28 +1,21 @@
package me.openphoto.android.app;
import java.net.URL;
import me.openphoto.android.app.model.Photo;
import me.openphoto.android.app.net.IOpenPhotoApi;
import me.openphoto.android.app.net.OpenPhotoApi;
import me.openphoto.android.app.net.Paging;
import me.openphoto.android.app.net.ReturnSize;
import me.openphoto.android.app.ui.widget.ActionBar;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.basic.DefaultOAuthConsumer;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ImageView;
import android.widget.Toast;
/**
@ -51,49 +44,59 @@ public class OAuthActivity extends Activity {
setContentView(R.layout.oauth);
mActionBar = (ActionBar) findViewById(R.id.actionbar);
// CookieSyncManager.createInstance(this);
// CookieSyncManager.getInstance().startSync();
// CookieManager cookieManager = CookieManager.getInstance();
// cookieManager.setAcceptCookie(true);
mWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = mWebView.getSettings();
webSettings.setSavePassword(false);
webSettings.setJavaScriptEnabled(true);
// webSettings.setSupportMultipleWindows(true);
// webSettings.setDatabaseEnabled(true);
// String databasePath = getApplicationContext().getDir("database",
// Context.MODE_PRIVATE)
// .getPath();
// webSettings.setDatabasePath(databasePath);
mWebView.setWebChromeClient(mWebChromeClient);
mWebView.setWebViewClient(mWebViewClient);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(false);
mOpenPhoto = OpenPhotoApi.createInstance(Preferences.getServer(this));
mOpenPhoto = Preferences.getApi(this);
String url = mOpenPhoto.getOAuthUrl(CALLBACK);
mWebView.loadUrl(url);
}
private final WebChromeClient mWebChromeClient = new WebChromeClient() {
private boolean mIsLoading = false;
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
if (newProgress < 100) {
if (!mIsLoading) {
mIsLoading = true;
mActionBar.startLoading();
}
} else if (mIsLoading) {
mActionBar.stopLoading();
mIsLoading = false;
}
public void onConsoleMessage(String message, int lineNumber, String sourceID) {
Log.e(TAG, "Error: " + message);
super.onConsoleMessage(message, lineNumber, sourceID);
}
};
private final WebViewClient mWebViewClient = new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mActionBar.stopLoading();
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
mActionBar.startLoading();
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
boolean result = true;
if ((url != null) && (url.startsWith(CALLBACK))) {
Uri uri = Uri.parse(url);
if (uri.getQueryParameter("token") != null) {
new PostTask().execute();
if (uri.getQueryParameter("oauth_token") != null) {
new PostTask(uri).execute();
} else {
Toast.makeText(OAuthActivity.this, "Error: " + uri.getQueryParameter("error"),
Toast.LENGTH_LONG).show();
@ -104,10 +107,15 @@ public class OAuthActivity extends Activity {
}
return result;
}
};
private class PostTask extends AsyncTask<Void, Void, Bitmap> {
private class PostTask extends AsyncTask<Void, Void, Boolean> {
private final Uri mUri;
private OAuthConsumer mUsedConsumer;
public PostTask(Uri uri) {
mUri = uri;
}
@Override
protected void onPreExecute() {
@ -116,34 +124,33 @@ public class OAuthActivity extends Activity {
}
@Override
protected Bitmap doInBackground(Void... params) {
IOpenPhotoApi api = OpenPhotoApi
.createInstance(Preferences.getServer(OAuthActivity.this));
protected Boolean doInBackground(Void... params) {
try {
Photo photo = api.getPhotos(new ReturnSize(600, 600), null, new Paging(1, 1))
.getPhotos().get(0);
// TODO do not use base, make getPhotos actually use a
// returnSize parameter that should be used then.
return BitmapFactory.decodeStream(new URL(photo
.getUrl("600x600")).openStream());
String oAuthConsumerKey = mUri.getQueryParameter("oauth_consumer_key");
String oAuthConsumerSecret = mUri.getQueryParameter("oauth_consumer_secret");
String oAuthToken = mUri.getQueryParameter("oauth_token");
String oAuthTokenSecret = mUri.getQueryParameter("oauth_token_secret");
String oAuthVerifier = mUri.getQueryParameter("oauth_verifier");
mUsedConsumer = new DefaultOAuthConsumer(oAuthConsumerKey,
oAuthConsumerSecret);
mUsedConsumer.setTokenWithSecret(oAuthToken, oAuthTokenSecret);
OAuthProvider provider = Preferences.getOAuthProvider(OAuthActivity.this);
provider.retrieveAccessToken(mUsedConsumer, oAuthVerifier);
return true;
} catch (Exception e) {
Log.w(TAG, "Error while getting image", e);
return null;
return false;
}
}
@Override
protected void onPostExecute(Bitmap result) {
protected void onPostExecute(Boolean result) {
mActionBar.stopLoading();
if (result != null) {
ImageView image = (ImageView) findViewById(R.id.image);
image.setImageBitmap(result);
image.setVisibility(View.VISIBLE);
} else {
Toast.makeText(OAuthActivity.this, "Could not download image",
Toast.LENGTH_LONG).show();
if (result.booleanValue()) {
Preferences.setLoginInformation(OAuthActivity.this, mUsedConsumer);
finish();
}
}
}
}

View file

@ -1,7 +1,14 @@
package me.openphoto.android.app;
import me.openphoto.android.app.net.IOpenPhotoApi;
import me.openphoto.android.app.net.OpenPhotoApi;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.basic.DefaultOAuthProvider;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
public class Preferences {
@ -31,10 +38,65 @@ public class Preferences {
.getBoolean(context.getString(R.string.setting_account_loggedin_key), false);
}
public static void setLoggedIn(Context context, boolean loggedIn) {
public static void logout(Context context) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putBoolean(context.getString(R.string.setting_account_loggedin_key), loggedIn)
.putBoolean(context.getString(R.string.setting_account_loggedin_key), false)
.commit();
context.getSharedPreferences("oauth", Context.MODE_PRIVATE)
.edit()
.clear()
.commit();
}
public static void setLoginInformation(Context context, OAuthConsumer consumer) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putBoolean(context.getString(R.string.setting_account_loggedin_key), true)
.commit();
context.getSharedPreferences("oauth", Context.MODE_PRIVATE)
.edit()
.putString(context.getString(R.string.setting_oauth_consumer_key),
consumer.getConsumerKey())
.putString(context.getString(R.string.setting_oauth_consumer_secret),
consumer.getConsumerSecret())
.putString(context.getString(R.string.setting_oauth_token),
consumer.getToken())
.putString(context.getString(R.string.setting_oauth_token_secret),
consumer.getTokenSecret())
.commit();
}
public static OAuthProvider getOAuthProvider(Context context) {
String serverUrl = getServer(context);
OAuthProvider provider = new DefaultOAuthProvider(
serverUrl + "/v1/oauth/token/request",
serverUrl + "/v1/oauth/token/access",
serverUrl + "/v1/oauth/authorize");
provider.setOAuth10a(true);
return provider;
}
public static IOpenPhotoApi getApi(Context context) {
IOpenPhotoApi api = OpenPhotoApi.createInstance(getServer(context));
if (isLoggedIn(context)) {
api.setOAuthConsumer(getOAuthConsumer(context));
}
return api;
}
private static OAuthConsumer getOAuthConsumer(Context context) {
if (!isLoggedIn(context)) {
throw new IllegalAccessError("User is not logged in, so can not call this method!");
}
SharedPreferences prefs = context.getSharedPreferences("oauth", Context.MODE_PRIVATE);
OAuthConsumer consumer = new CommonsHttpOAuthConsumer(
prefs.getString(context.getString(R.string.setting_oauth_consumer_key), null),
prefs.getString(context.getString(R.string.setting_oauth_consumer_secret), null));
consumer.setTokenWithSecret(
prefs.getString(context.getString(R.string.setting_oauth_token), null),
prefs.getString(context.getString(R.string.setting_oauth_token_secret), null));
return consumer;
}
}

View file

@ -6,7 +6,6 @@ package me.openphoto.android.app;
import me.openphoto.android.app.model.Tag;
import me.openphoto.android.app.net.IOpenPhotoApi;
import me.openphoto.android.app.net.OpenPhotoApi;
import me.openphoto.android.app.net.TagsResponse;
import me.openphoto.android.app.ui.adapter.EndlessAdapter;
import me.openphoto.android.app.ui.widget.ActionBar;
@ -88,8 +87,7 @@ public class SearchActivity extends Activity implements OnItemClickListener {
public TagsAdapter() {
super();
mOpenPhotoApi = OpenPhotoApi
.createInstance(Preferences.getServer(SearchActivity.this));
mOpenPhotoApi = Preferences.getApi(SearchActivity.this);
}
@Override

View file

@ -36,15 +36,23 @@ public class SettingsActivity extends PreferenceActivity implements OnPreference
@Override
protected void onResume() {
super.onResume();
refreshLoginPreferenceTitle();
}
private void refreshLoginPreferenceTitle() {
mLoginPreference.setTitle(Preferences.isLoggedIn(this) ?
R.string.setting_account_loggedin_logout : R.string.setting_account_loggedin_login);
}
// TODO when server is changed it should delete the login information
@Override
public boolean onPreferenceClick(Preference preference) {
if (getString(R.string.setting_account_loggedin_key).equals(preference.getKey())) {
if (Preferences.isLoggedIn(this)) {
// TODO show logout confirmation dialog
Preferences.logout(this);
refreshLoginPreferenceTitle();
} else {
startActivity(new Intent(this, OAuthActivity.class));
}

View file

@ -5,7 +5,6 @@ import java.io.File;
import java.io.IOException;
import java.util.Date;
import me.openphoto.android.app.net.OpenPhotoApi;
import me.openphoto.android.app.net.UploadMetaData;
import me.openphoto.android.app.net.UploadResponse;
import me.openphoto.android.app.util.FileUtils;
@ -188,8 +187,7 @@ public class UploadActivity extends Activity implements OnClickListener {
// TODO add private and effects aviary
try {
return OpenPhotoApi.createInstance(Preferences.getServer(UploadActivity.this))
.uploadPhoto(params[0], metaData);
return Preferences.getApi(UploadActivity.this).uploadPhoto(params[0], metaData);
} catch (Exception e) {
Log.e(TAG, "Error while uploading", e);
}

View file

@ -5,7 +5,6 @@
package me.openphoto.android.app;
import me.openphoto.android.app.model.Photo;
import me.openphoto.android.app.net.OpenPhotoApi;
import me.openphoto.android.app.net.PhotoResponse;
import me.openphoto.android.app.net.ReturnSize;
import me.openphoto.android.app.ui.lib.ImageStorage;
@ -86,9 +85,8 @@ public class ViewPhotoActivity extends Activity implements OnClickListener {
@Override
protected Photo doInBackground(Void... params) {
try {
PhotoResponse response = OpenPhotoApi.createInstance(
Preferences.getServer(ViewPhotoActivity.this)).getPhoto(mPhoto.getId(),
new ReturnSize(1024, 1024));
PhotoResponse response = Preferences.getApi(ViewPhotoActivity.this)
.getPhoto(mPhoto.getId(), new ReturnSize(1024, 1024));
return response.getPhoto();
} catch (Exception e) {
return null;

View file

@ -7,6 +7,7 @@ import java.io.UnsupportedEncodingException;
import java.util.List;
import me.openphoto.android.app.net.ApiRequest.Parameter;
import oauth.signpost.OAuthConsumer;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
@ -33,6 +34,7 @@ import org.apache.http.protocol.HTTP;
*/
public class ApiBase {
private final String mBaseUrl;
private OAuthConsumer mOAuthConsumer;
/**
* Instantiates a new ApiBase object.
@ -46,6 +48,16 @@ public class ApiBase {
mBaseUrl = baseUrl;
}
/**
* Sets the OAuthConsumer when the calls with the server need to be
* authenticated.
*
* @param oAuthConsumer
*/
public void setOAuthConsumer(OAuthConsumer oAuthConsumer) {
mOAuthConsumer = oAuthConsumer;
}
/**
* Gets the base url of the used API.
*
@ -70,7 +82,14 @@ public class ApiBase {
HttpUriRequest httpRequest = createHttpRequest(request);
httpRequest.getParams().setBooleanParameter("http.protocol.expect-continue", false);
if (mOAuthConsumer != null) {
try {
mOAuthConsumer.sign(httpRequest);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return new ApiResponse(httpClient.execute(httpRequest));
}

View file

@ -5,11 +5,21 @@ import java.io.File;
import java.io.IOException;
import java.util.Collection;
import oauth.signpost.OAuthConsumer;
import org.apache.http.client.ClientProtocolException;
import org.json.JSONException;
public interface IOpenPhotoApi {
/**
* Sets the OAuthConsumer when the calls with the server need to be
* authenticated.
*
* @param oAuthConsumer
*/
void setOAuthConsumer(OAuthConsumer oAuthConsumer);
/**
* @return tags which are used on the server
* @throws ClientProtocolException

View file

@ -6,6 +6,8 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import oauth.signpost.OAuthConsumer;
import org.apache.http.client.ClientProtocolException;
import org.json.JSONException;
import org.json.JSONObject;
@ -36,6 +38,21 @@ public class OpenPhotoApi extends ApiBase implements IOpenPhotoApi {
}
}
/*
* (non-Javadoc)
* @see
* me.openphoto.android.app.net.IOpenPhotoApi#setOAuthConsumer(oauth.signpost
* .OAuthConsumer)
*/
@Override
public void setOAuthConsumer(OAuthConsumer oAuthConsumer) {
super.setOAuthConsumer(oAuthConsumer);
}
/*
* (non-Javadoc)
* @see me.openphoto.android.app.net.IOpenPhotoApi#getTags()
*/
@Override
public TagsResponse getTags() throws ClientProtocolException, IOException,
IllegalStateException, JSONException {
@ -68,7 +85,7 @@ public class OpenPhotoApi extends ApiBase implements IOpenPhotoApi {
*/
@Override
public String getOAuthUrl(String callback) {
return getBaseUrl() + "/v1/oauth/authorize";
return getBaseUrl() + "/v1/oauth/authorize?mobile=1&oauth_callback=" + callback;
}
/*

View file

@ -6,7 +6,6 @@ import java.util.Calendar;
import me.openphoto.android.app.Preferences;
import me.openphoto.android.app.net.IOpenPhotoApi;
import me.openphoto.android.app.net.OpenPhotoApi;
import me.openphoto.android.app.net.UploadMetaData;
import me.openphoto.android.app.provider.UploadsProvider;
import me.openphoto.android.app.util.ImageUtils;
@ -31,7 +30,7 @@ public class UploaderService extends IntentService {
@Override
public void onCreate() {
super.onCreate();
mApi = OpenPhotoApi.createInstance(Preferences.getServer(this));
mApi = Preferences.getApi(this);
}
@Override