fixed oauth login, moved to header auth, added user v2 implementation

This commit is contained in:
nuclearfog 2022-01-07 14:47:56 +01:00
parent b30838ebd1
commit 16ee4bf4c3
No known key found for this signature in database
GPG Key ID: AA0271FBE406DB98
16 changed files with 617 additions and 421 deletions

View File

@ -67,7 +67,6 @@ dependencies {
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'org.twitter4j:twitter4j-core:4.0.7'
implementation 'com.github.takke:twitter4j-v2:1.0.3'
//noinspection GradleDependency
implementation 'com.squareup.picasso:picasso:2.8'
implementation 'com.larswerkman:LicenseView:1.1'

View File

@ -22,6 +22,7 @@ import androidx.appcompat.widget.Toolbar;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.Registration;
import org.nuclearfog.twidda.backend.api.Twitter;
import org.nuclearfog.twidda.backend.apiold.EngineException;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorHandler;
@ -54,6 +55,8 @@ public class LoginActivity extends AppCompatActivity implements OnClickListener
private EditText pinInput;
private ViewGroup root;
private String requestToken;
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(AppStyles.setFontScale(newBase));
@ -156,7 +159,7 @@ public class LoginActivity extends AppCompatActivity implements OnClickListener
Toast.makeText(this, R.string.info_login_to_twitter, LENGTH_LONG).show();
String twitterPin = pinInput.getText().toString();
registerAsync = new Registration(this);
registerAsync.execute(twitterPin);
registerAsync.execute(requestToken, twitterPin);
} else {
Toast.makeText(this, R.string.error_enter_pin, LENGTH_LONG).show();
}
@ -166,9 +169,11 @@ public class LoginActivity extends AppCompatActivity implements OnClickListener
/**
* Called when a twitter login link was created
*
* @param link Link to twitter login page
* @param requestToken temporary request token
*/
public void connect(String link) {
public void connect(String requestToken) {
this.requestToken = requestToken;
String link = Twitter.REQUEST_URL + requestToken;
Intent loginIntent = new Intent(ACTION_VIEW, Uri.parse(link));
try {
startActivity(loginIntent);

View File

@ -3,9 +3,11 @@ package org.nuclearfog.twidda.backend;
import android.os.AsyncTask;
import org.nuclearfog.twidda.activities.LoginActivity;
import org.nuclearfog.twidda.backend.api.TwitterImpl;
import org.nuclearfog.twidda.backend.api.Twitter;
import org.nuclearfog.twidda.database.AccountDatabase;
import org.nuclearfog.twidda.database.AppDatabase;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.User;
import java.lang.ref.WeakReference;
@ -19,7 +21,8 @@ public class Registration extends AsyncTask<String, Void, String> {
private WeakReference<LoginActivity> callback;
private AccountDatabase accountDB;
private TwitterImpl twitter;
private AppDatabase database;
private Twitter twitter;
private GlobalSettings settings;
/**
@ -32,8 +35,9 @@ public class Registration extends AsyncTask<String, Void, String> {
this.callback = new WeakReference<>(activity);
// init database and storage
accountDB = new AccountDatabase(activity);
database = new AppDatabase(activity);
settings = GlobalSettings.getInstance(activity);
twitter = TwitterImpl.get(activity);
twitter = Twitter.get(activity);
}
@ -42,15 +46,15 @@ public class Registration extends AsyncTask<String, Void, String> {
try {
// check if we need to backup current session
if (settings.isLoggedIn() && !accountDB.exists(settings.getCurrentUserId())) {
String[] tokens = settings.getCurrentUserAccessToken();
accountDB.setLogin(settings.getCurrentUserId(), tokens[0], tokens[1]);
accountDB.setLogin(settings.getCurrentUserId(), settings.getAccessToken(), settings.getTokenSecret());
}
// no PIN means we need to request a token to login
if (param.length == 0) {
return twitter.getRequestURL();
return twitter.getRequestToken();
}
// login with pin
twitter.login(param[0]);
User user = twitter.login(param[0], param[1]);
database.storeUser(user);
return "";
} catch (Exception err) {
err.printStackTrace();

View File

@ -4,6 +4,8 @@ import android.os.AsyncTask;
import androidx.annotation.Nullable;
import org.nuclearfog.twidda.backend.api.Twitter;
import org.nuclearfog.twidda.backend.api.TwitterException;
import org.nuclearfog.twidda.backend.apiold.EngineException;
import org.nuclearfog.twidda.backend.apiold.TwitterEngine;
import org.nuclearfog.twidda.backend.lists.Users;
@ -68,6 +70,7 @@ public class UserLoader extends AsyncTask<Long, Void, Users> {
private EngineException twException;
private final WeakReference<UserFragment> callback;
private final TwitterEngine mTwitter;
private Twitter mTwitter2;
private final Type type;
private final String search;
@ -78,6 +81,7 @@ public class UserLoader extends AsyncTask<Long, Void, Users> {
super();
this.callback = new WeakReference<>(callback);
mTwitter = TwitterEngine.getInstance(callback.getContext());
mTwitter2 = Twitter.get(callback.getContext());
this.type = type;
this.search = search;
this.id = id;
@ -96,10 +100,10 @@ public class UserLoader extends AsyncTask<Long, Void, Users> {
return mTwitter.getFollowing(id, cursor);
case RETWEET:
return mTwitter.getRetweeter(id, cursor);
return mTwitter2.getRetweetingUsers(id);
case FAVORIT:
return mTwitter.getFavoriter(id, cursor);
return mTwitter2.getLikingUsers(id);
case SEARCH:
return mTwitter.searchUsers(search, cursor);
@ -119,6 +123,8 @@ public class UserLoader extends AsyncTask<Long, Void, Users> {
}
} catch (EngineException twException) {
this.twException = twException;
} catch (TwitterException err) {
}
return null;
}

View File

@ -0,0 +1,300 @@
package org.nuclearfog.twidda.backend.api;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.nuclearfog.twidda.backend.lists.Users;
import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.backend.utils.TLSSocketFactory;
import org.nuclearfog.twidda.backend.utils.Tokens;
import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.database.GlobalSettings;
import java.io.IOException;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.TreeSet;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* new API implementation to replace twitter4j and add version 2.0 support
*
* @author nuclearfog
*/
public class Twitter {
private static final String OAUTH = "1.0";
private static final String API = "https://api.twitter.com/";
private static final String AUTHENTICATE = API + "oauth/authenticate";
private static final String REQUEST_TOKEN = API + "oauth/request_token";
private static final String OAUTH_VERIFIER = API + "oauth/access_token";
private static final String CREDENTIALS = API + "1.1/account/verify_credentials.json";
public static final String REQUEST_URL = AUTHENTICATE + "?oauth_token=";
public static final String SIGNATURE_ALG = "HMAC-SHA256";
private static Twitter instance;
private OkHttpClient client;
private GlobalSettings settings;
private Tokens tokens;
private Twitter(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
try {
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
factory.init((KeyStore) null);
X509TrustManager manager = (X509TrustManager) factory.getTrustManagers()[0];
client = new OkHttpClient().newBuilder().sslSocketFactory(new TLSSocketFactory(), manager).build();
} catch (Exception e) {
client = new OkHttpClient().newBuilder().build();
}
} else {
client = new OkHttpClient().newBuilder().build();
}
settings = GlobalSettings.getInstance(context);
tokens = Tokens.getInstance(context);
}
/**
* get singleton instance
*
* @return instance of this class
*/
public static Twitter get(Context context) {
if (instance == null) {
instance = new Twitter(context);
}
return instance;
}
/**
* request temporary access token to pass it to the Twitter login page
*
* @return a temporary access token created by Twitter
*/
public String getRequestToken() throws TwitterException {
try {
Response response = post(REQUEST_TOKEN);
if (response.code() == 200 && response.body() != null) {
String res = response.body().string();
Uri uri = Uri.parse(AUTHENTICATE + "?" + res);
return uri.getQueryParameter("oauth_token");
} else {
throw new TwitterException(response.code());
}
} catch (IOException e) {
throw new TwitterException(e);
}
}
/**
* login to twitter using pin and add store access tokens
*
* @param pin pin from the login website
*/
public User login(String oauth_token, String pin) throws TwitterException {
try {
if (oauth_token == null)
throw new TwitterException(TwitterException.TOKEN_NOT_SET);
String paramPin = "oauth_verifier=" + pin;
String paramToken = "oauth_token=" + oauth_token;
Response response = post(OAUTH_VERIFIER, paramPin, paramToken);
if (response.code() == 200 && response.body() != null) {
String res = response.body().string();
// extrect tokens from link
Uri uri = Uri.parse(OAUTH_VERIFIER + "?" + res);
settings.setAccessToken(uri.getQueryParameter("oauth_token"));
settings.setTokenSecret(uri.getQueryParameter("oauth_token_secret"));
settings.setUserId(Long.parseLong(uri.getQueryParameter("user_id")));
settings.setogin(true);
return getCredentials();
} else {
throw new TwitterException(response.code());
}
} catch (IOException e) {
throw new TwitterException(e);
}
}
/**
* get credentials of the current user
*
* @return current user
*/
public User getCredentials() throws TwitterException {
try {
Response response = get(CREDENTIALS);
if (response.code() == 200 && response.body() != null) {
JSONObject json = new JSONObject(response.body().string());
return new UserV1(json);
} else {
throw new TwitterException(response.code());
}
} catch (IOException err) {
throw new TwitterException(err);
} catch (JSONException err) {
throw new TwitterException(err);
}
}
/**
* get users retweeting a tweet
*
* @param tweetId ID of the tweet
* @return user list
*/
public Users getRetweetingUsers(long tweetId) throws TwitterException {
String endpoint = API + "2/tweets/" + tweetId + "/retweeted_by";
return getUsers(endpoint);
}
/**
* get users liking a tweet
*
* @param tweetId ID of the tweet
* @return user list
*/
public Users getLikingUsers(long tweetId) throws TwitterException {
String endpoint = API + "2/tweets/" + tweetId + "/liking_users";
return getUsers(endpoint);
}
/**
* get a list of twitter users
*
* @param endpoint endpoint url to get the user data from
* @return user list
*/
private Users getUsers(String endpoint) throws TwitterException {
try {
Response response = get(endpoint, UserV2.PARAMS);
if (response.code() == 200 && response.body() != null) {
JSONObject json = new JSONObject(response.body().string());
JSONArray array = json.getJSONArray("data");
Users users = new Users();
long homeId = settings.getCurrentUserId();
for (int i = 0 ; i < array.length() ; i++) {
users.add(new UserV2(array.getJSONObject(i), homeId));
}
return users;
} else {
throw new TwitterException(response.code());
}
} catch (IOException err) {
throw new TwitterException(err);
} catch (JSONException err) {
throw new TwitterException(err);
}
}
/**
* create and call POST endpoint
*
* @param endpoint endpoint url
* @return http resonse
*/
private Response post(String endpoint, String... params) throws IOException {
String authHeader = buildHeader("POST", endpoint, params);
String url = appendParams(endpoint, params);
RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "");
Request request = new Request.Builder().url(url).addHeader("Authorization", authHeader).post(body).build();
return client.newCall(request).execute();
}
/**
* create and call GET endpoint
*
* @param endpoint endpoint url
* @return http response
*/
private Response get(String endpoint, String... params) throws IOException {
String authHeader = buildHeader("GET", endpoint, params);
String url = appendParams(endpoint, params);
Request request = new Request.Builder().url(url).addHeader("Authorization", authHeader).get().build();
return client.newCall(request).execute();
}
/**
* create http header with credentials and signature
*
* @param method endpoint method to call
* @param endpoint endpoint url
* @param params parameter to add to signature
* @return header string
*/
private String buildHeader(String method, String endpoint, String... params) {
String timeStamp = StringTools.getTimestamp();
String random = StringTools.getRandomString();
String signkey = tokens.getConsumerSec() + "&";
String oauth_token_param = "";
// init default parameters
TreeSet<String> sortedParams = new TreeSet<>();
sortedParams.add("oauth_callback=oob");
sortedParams.add("oauth_consumer_key=" + tokens.getConsumerKey());
sortedParams.add("oauth_nonce=" + random);
sortedParams.add("oauth_signature_method=" + SIGNATURE_ALG);
sortedParams.add("oauth_timestamp=" + timeStamp);
sortedParams.add("oauth_version=" + OAUTH);
// add custom parameters
sortedParams.addAll(Arrays.asList(params));
// only add tokens if there is no login process
if (!REQUEST_TOKEN.equals(endpoint) && !OAUTH_VERIFIER.equals(endpoint)) {
sortedParams.add("oauth_token=" + settings.getAccessToken());
oauth_token_param = ", oauth_token=\"" + settings.getAccessToken() + "\"";
signkey += settings.getTokenSecret();
}
// build string with sorted parameters
StringBuilder paramStr = new StringBuilder();
for (String param : sortedParams)
paramStr.append(param).append('&');
paramStr.deleteCharAt(paramStr.length() - 1);
// calculate oauth signature
String signature = StringTools.sign(method, endpoint, paramStr.toString(), signkey);
// create header string
return "OAuth oauth_callback=\"oob\"" +
", oauth_consumer_key=\"" + tokens.getConsumerKey() + "\"" +
", oauth_nonce=\""+ random + "\"" +
", oauth_signature=\"" + signature + "\"" +
", oauth_signature_method=\""+ SIGNATURE_ALG + "\"" +
", oauth_timestamp=\"" + timeStamp + "\""
+ oauth_token_param +
", oauth_version=\"" + OAUTH + "\"";
}
/**
* build url with param
*
* @param url url without parameters
* @param params parameters
* @return url with parameters
*/
private String appendParams(String url, String[] params) {
if (params.length > 0) {
StringBuilder result = new StringBuilder(url);
result.append('?');
for (String param : params)
result.append(param).append('&');
result.deleteCharAt(result.length() - 1);
return result.toString();
}
return url;
}
}

View File

@ -0,0 +1,24 @@
package org.nuclearfog.twidda.backend.api;
import java.io.IOException;
public class TwitterException extends Exception {
public static final int TOKEN_NOT_SET = 600;
private int httpCode;
TwitterException(Exception e) {
super(e);
httpCode = -1;
}
TwitterException(int httpCode) {
this.httpCode = httpCode;
}
public int getCode() {
return httpCode;
}
}

View File

@ -1,196 +0,0 @@
package org.nuclearfog.twidda.backend.api;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import org.json.JSONException;
import org.json.JSONObject;
import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.backend.utils.TLSSocketFactory;
import org.nuclearfog.twidda.backend.utils.Tokens;
import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.database.GlobalSettings;
import java.io.IOException;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.TreeSet;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* new API implementation to replace twitter4j and add version 2.0 support
*
* @author nuclearfog
*/
public class TwitterImpl {
public static final String HASH = "HMAC-SHA256";
private static final String OAUTH = "1.0";
private static final String API = "https://api.twitter.com/";
private static final String REQUEST_TOKEN = API + "oauth/request_token";
private static final String AUTHENTICATE = API + "oauth/authenticate";
private static final String OAUTH_VERIFIER = API + "oauth/access_token";
private static final String CREDENTIALS = API + "1.1/account/verify_credentials.json";
private static TwitterImpl instance;
private GlobalSettings settings;
private Tokens tokens;
private OkHttpClient client;
private TwitterImpl(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
try {
TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
factory.init((KeyStore) null);
X509TrustManager manager = (X509TrustManager) factory.getTrustManagers()[0];
client = new OkHttpClient().newBuilder().sslSocketFactory(new TLSSocketFactory(), manager).build();
} catch (Exception e) {
client = new OkHttpClient().newBuilder().build();
}
} else {
client = new OkHttpClient().newBuilder().build();
}
settings = GlobalSettings.getInstance(context);
tokens = Tokens.getInstance(context);
}
/**
* get singleton instance
*
* @return instance of this class
*/
public static TwitterImpl get(Context context) {
if (instance == null) {
instance = new TwitterImpl(context);
}
return instance;
}
/**
* request login page url and generate forst token
*
* @return url to the login page
*/
public String getRequestURL() throws IOException {
Response response = post(REQUEST_TOKEN, "oauth_callback=oob");
if (response.code() == 200 && response.body() != null) {
String res = response.body().string();
Uri uri = Uri.parse(AUTHENTICATE + "?" + res);
String token = uri.getQueryParameter("oauth_token");
tokens.setTokens(token);
return AUTHENTICATE + "?oauth_token=" + token;
} else {
// todo add exception
}
return "";
}
/**
* login to twitter using pin and add store access tokens
*
* @param pin pin from the login website
*/
public void login(String pin) throws IOException, JSONException {
Response response = post(OAUTH_VERIFIER, "oauth_verifier=" + pin, "oauth_token=" + tokens.getToken());
if (response.code() == 200 && response.body() != null) {
String res = response.body().string();
Uri uri = Uri.parse(OAUTH_VERIFIER + "?" + res);
String token = uri.getQueryParameter("oauth_token");
String tokenSec = uri.getQueryParameter("oauth_token_secret");
tokens.setTokens(token, tokenSec);
settings.setUserId(getCredentials().getId());
} else {
// todo add exception
}
}
/**
* get credentials of the current user
*
* @return current user
*/
public User getCredentials() throws IOException, JSONException {
Response response = get(CREDENTIALS);
if (response.code() == 200 && response.body() != null) {
JSONObject json = new JSONObject(response.body().string());
return new UserV1(json, -1);
} else {
throw new IOException("");
}
}
/**
* create and call POST endpoint
*
* @param endpoint endpoint url
* @param add additional parameters
* @return http resonse
*/
private Response post(String endpoint, String... add) throws IOException {
String oauth_sec = "";
if (settings.isLoggedIn()) {
oauth_sec = tokens.getTokenSec();
}
String param = buildParamString(add);
param += "&oauth_signature=" + StringTools.signPost(endpoint, param, tokens.getConsumerSec() + "&" +oauth_sec);
RequestBody body = RequestBody.create(MediaType.parse("text/plain"), "");
Request request = new Request.Builder().url(endpoint + "?" + param).post(body).build();
return client.newCall(request).execute();
}
/**
* create and call GET endpoint
*
* @param endpoint endpoint url
* @param add additional parameters
* @return http response
*/
private Response get(String endpoint, String... add) throws IOException {
String oauth_sec = "";
if (settings.isLoggedIn()) {
oauth_sec = tokens.getTokenSec();
}
String param = buildParamString(add);
param += "&oauth_signature=" + StringTools.signGet(endpoint, param, tokens.getConsumerSec() + "&" + oauth_sec);
Request request = new Request.Builder().url(endpoint + "?" + param).get().build();
return client.newCall(request).execute();
}
/**
* build twitter API parameters
*
* @param add additional parameters
* @return parameter string
*/
private String buildParamString(String... add) {
// sort parameters
TreeSet<String> params = new TreeSet<>();
params.add("oauth_consumer_key=" + tokens.getConsumerKey());
params.add("oauth_nonce=" + StringTools.getRandomString());
params.add("oauth_signature_method=" + HASH);
params.add("oauth_timestamp=" + StringTools.getTimestamp());
params.add("oauth_version=" + OAUTH);
params.addAll(Arrays.asList(add));
if (settings.isLoggedIn()) {
params.add("oauth_token=" + tokens.getToken());
}
// append sorted parameters to string
StringBuilder param = new StringBuilder();
for (String e : params)
param.append(e).append('&');
return param.deleteCharAt(param.length() - 1).toString();
}
}

View File

@ -6,6 +6,11 @@ import org.nuclearfog.twidda.model.User;
import java.text.SimpleDateFormat;
import java.util.Locale;
/**
* API 1.1 implementation of User
*
* @author nuclearfog
*/
class UserV1 implements User {
private static final SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.US);
@ -31,6 +36,12 @@ class UserV1 implements User {
UserV1(JSONObject json, long twitterId) {
this(json);
isCurrentUser = twitterId == userID;
}
UserV1(JSONObject json) {
String bannerLink = json.optString("profile_banner_url");
description = json.optString("description");
username = json.optString("name");
@ -46,12 +57,12 @@ class UserV1 implements User {
favorCount = json.optInt("favourites_count");
followReqSent = json.optBoolean("follow_request_sent");
defaultImage = json.optBoolean("default_profile_image");
isCurrentUser = twitterId == userID;
if (bannerLink.length() > 4)
profileBannerUrl = bannerLink.substring(0, bannerLink.length() - 4);
profileUrl = json.optString("profile_image_url_https");
setDate(json.optString("created_at"));
isCurrentUser = true;
if (bannerLink.length() > 4) {
profileBannerUrl = bannerLink.substring(0, bannerLink.length() - 4);
}
}

View File

@ -0,0 +1,168 @@
package org.nuclearfog.twidda.backend.api;
import org.json.JSONObject;
import org.nuclearfog.twidda.model.User;
import java.text.SimpleDateFormat;
import java.util.Locale;
/**
* implementation of User accessed by API 2.0
*
* @author nuclearfog
*/
class UserV2 implements User {
/**
* extra parameters required to fetch additional data
*/
public static final String PARAMS = "user.fields=profile_image_url";
// ISO8601 time format used by twitter
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
private long userID;
private long created;
private String username;
private String screenName;
private String description;
private String location;
private String profileUrl;
private String profileImageUrl;
private String profileBannerUrl;
private int following;
private int follower;
private int tweetCount;
private int favorCount;
private boolean isCurrentUser;
private boolean isVerified;
private boolean isProtected;
private boolean followReqSent;
private boolean defaultImage;
UserV2(JSONObject json, long twitterId) {
userID = json.optLong("id");
username = json.optString("name");
screenName = json.optString("username"); // username -> screenname
isProtected = json.optBoolean("protected");
location = json.optString("location");
profileUrl = json.optString("profile_image_url");
description = json.optString("description");
isVerified = json.optBoolean("verified");
profileImageUrl = json.optString("profile_image_url");
following = json.optInt("public_metrics.following_count");
follower = json.optInt("public_metrics.followers_count");
tweetCount = json.optInt("public_metrics.tweet_count");
isCurrentUser = userID == twitterId;
setDate(json.optString("created_at"));
// not defined jet in V2
profileBannerUrl = "";
favorCount = 0;
followReqSent = false;
defaultImage = true;
}
@Override
public long getId() {
return userID;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getScreenname() {
return screenName;
}
@Override
public long getCreatedAt() {
return created;
}
@Override
public String getImageUrl() {
return profileImageUrl;
}
@Override
public String getBannerUrl() {
return profileBannerUrl;
}
@Override
public String getDescription() {
return description;
}
@Override
public String getLocation() {
return location;
}
@Override
public String getProfileUrl() {
return profileUrl;
}
@Override
public boolean isVerified() {
return isVerified;
}
@Override
public boolean isProtected() {
return isProtected;
}
@Override
public boolean followRequested() {
return followReqSent;
}
@Override
public int getFollowing() {
return following;
}
@Override
public int getFollower() {
return follower;
}
@Override
public int getTweetCount() {
return tweetCount;
}
@Override
public int getFavoriteCount() {
return favorCount;
}
@Override
public boolean hasDefaultProfileImage() {
return defaultImage;
}
@Override
public boolean isCurrentUser() {
return isCurrentUser;
}
/**
* set time of account creation
*
* @param dateStr date string from twitter
*/
private void setDate(String dateStr) {
try {
created = sdf.parse(dateStr).getTime();
} catch (Exception e) {
// make date invalid so it will be not shown
e.printStackTrace();
}
}
}

View File

@ -15,7 +15,6 @@ import org.nuclearfog.twidda.backend.lists.UserLists;
import org.nuclearfog.twidda.backend.utils.ProxySetup;
import org.nuclearfog.twidda.backend.utils.TLSSocketFactory;
import org.nuclearfog.twidda.backend.utils.Tokens;
import org.nuclearfog.twidda.database.AccountDatabase;
import org.nuclearfog.twidda.database.ExcludeDatabase;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.Location;
@ -42,7 +41,6 @@ import twitter4j.DirectMessage;
import twitter4j.DirectMessageList;
import twitter4j.GeoLocation;
import twitter4j.IDs;
import twitter4j.LikesExKt;
import twitter4j.PagableResponseList;
import twitter4j.Paging;
import twitter4j.Query;
@ -53,10 +51,7 @@ import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.UploadedMedia;
import twitter4j.User2;
import twitter4j.UsersResponse;
import twitter4j.auth.AccessToken;
import twitter4j.auth.RequestToken;
import twitter4j.conf.ConfigurationBuilder;
/**
@ -68,10 +63,7 @@ public class TwitterEngine {
private static final TwitterEngine mTwitter = new TwitterEngine();
@Nullable
private RequestToken reqToken;
private GlobalSettings settings;
private AccountDatabase accountDB;
private ExcludeDatabase excludeDB;
private Tokens tokens;
private Twitter twitter;
@ -123,13 +115,13 @@ public class TwitterEngine {
// initialize database and settings
mTwitter.settings = GlobalSettings.getInstance(context);
mTwitter.tokens = Tokens.getInstance(context);
mTwitter.accountDB = new AccountDatabase(context);
mTwitter.excludeDB = new ExcludeDatabase(context);
// check if already logged in
if (mTwitter.settings.isLoggedIn()) {
// init login access
String[] keys = mTwitter.settings.getCurrentUserAccessToken();
AccessToken token = new AccessToken(keys[0], keys[1]);
String accessToken = mTwitter.settings.getAccessToken();
String tokenSecret = mTwitter.settings.getTokenSecret();
AccessToken token = new AccessToken(accessToken, tokenSecret);
mTwitter.initTwitter(token);
} else {
// init empty session
@ -139,21 +131,6 @@ public class TwitterEngine {
return mTwitter;
}
/**
* get singleton instance with empty session
*
* @return TwitterEngine Instance
*/
public static TwitterEngine getEmptyInstance(Context context) {
// initialize storage
mTwitter.settings = GlobalSettings.getInstance(context);
mTwitter.accountDB = new AccountDatabase(context);
// init empty session
mTwitter.isInitialized = false;
mTwitter.initTwitter(null);
return mTwitter;
}
/**
* reset Twitter state
*/
@ -161,54 +138,6 @@ public class TwitterEngine {
mTwitter.isInitialized = false;
}
/**
* Request Registration Website
*
* @return Link to App Registration
* @throws EngineException if internet connection is unavailable
*/
public String request() throws EngineException {
try {
if (reqToken == null) {
// request token without redirecting to the callback url
reqToken = twitter.getOAuthRequestToken("oob");
}
} catch (Exception err) {
throw new EngineException(err);
}
return reqToken.getAuthenticationURL();
}
/**
* Get account access keys, store them and initialize Twitter login
*
* @param twitterPin PIN from the twitter login page, after successful login
* @throws EngineException if pin is false or request token is null
*/
public void initialize(String twitterPin) throws EngineException {
try {
// check if corresponding request key is valid
if (reqToken != null) {
// get login keys
AccessToken accessToken = twitter.getOAuthAccessToken(reqToken, twitterPin);
String key1 = accessToken.getToken();
String key2 = accessToken.getTokenSecret();
// init twitter login
initTwitter(new AccessToken(key1, key2));
// save login to storage and database
settings.setConnection(key1, key2, twitter.getId());
accountDB.setLogin(twitter.getId(), key1, key2);
// request token is not needed anymore
reqToken = null;
} else {
// request token does not exist, open login page first
throw new EngineException(EngineException.InternalErrorType.TOKENNOTSET);
}
} catch (Exception err) {
throw new EngineException(err);
}
}
/**
* Get Home Timeline
*
@ -841,54 +770,6 @@ public class TwitterEngine {
}
}
/**
* Get User who retweeted a Tweet
*
* @param tweetID Tweet ID
* @return List of users
* @throws EngineException if Access is unavailable
*/
public Users getRetweeter(long tweetID, long cursor) throws EngineException {
try {
int load = settings.getListSize();
IDs userIDs = twitter.getRetweeterIds(tweetID, load, cursor);
long[] ids = userIDs.getIDs();
long prevCursor = cursor > 0 ? cursor : 0;
long nextCursor = userIDs.getNextCursor(); // fixme next cursor always zero
Users result = new Users(prevCursor, nextCursor);
if (ids.length > 0) {
result.addAll(convertUserList(twitter.lookupUsers(ids)));
}
return result;
} catch (Exception err) {
throw new EngineException(err);
}
}
/**
* get user who liked a tweet
*
* @param tweetId Tweet ID
* @return list of users liking a tweet
* @throws EngineException if Access is unavailable
*/
public Users getFavoriter(long tweetId, long cursor) throws EngineException {
try {
UsersResponse response = LikesExKt.getLikingUsers(twitter, tweetId, null, null, null);
List<User2> users = response.getUsers();
long[] ids = new long[users.size()];
for (int i = 0 ; i < ids.length ; i++)
ids[i] = users.get(i).getId();
// lookup users with Twitter4J for maximum compability
Users result = new Users(cursor, 0);
result.addAll(convertUserList(twitter.lookupUsers(ids)));
return result;
} catch (TwitterException err) {
throw new EngineException(err);
}
}
/**
* get list of Direct Messages
*

View File

@ -1,6 +1,6 @@
package org.nuclearfog.twidda.backend.utils;
import static org.nuclearfog.twidda.backend.api.TwitterImpl.HASH;
import static org.nuclearfog.twidda.backend.api.Twitter.SIGNATURE_ALG;
import android.util.Base64;
@ -161,28 +161,16 @@ public final class StringTools {
}
/**
* sign GET API request
* generate signature for oauth
*
* @param endpoint API endpoint
* @param param API parameter to sign
* @param method method e.g. POST,GET or PUT
* @param endpoint endpoint URL
* @param param parameter
* @param keyString key used to sign
* @return sign string
* @return key signature
*/
public static String signGet(String endpoint, String param, String keyString) {
String input = "GET&" + encode(endpoint) + "&" + encode(param);
return encode(computeSignature(input, keyString));
}
/**
* sign POST API request
*
* @param endpoint API endpoint
* @param param API parameter to sign
* @param keyString key used to sign
* @return sign string
*/
public static String signPost(String endpoint, String param, String keyString) {
String input = "POST&" + encode(endpoint) + "&" + encode(param);
public static String sign(String method, String endpoint, String param, String keyString) {
String input = method + "&" + encode(endpoint) + "&" + encode(param);
return encode(computeSignature(input, keyString));
}
@ -195,8 +183,8 @@ public final class StringTools {
*/
private static String computeSignature(String baseString, String keyString) {
try {
SecretKey secretKey = new SecretKeySpec(keyString.getBytes(), HASH);
Mac mac = Mac.getInstance(HASH);
SecretKey secretKey = new SecretKeySpec(keyString.getBytes(), SIGNATURE_ALG);
Mac mac = Mac.getInstance(SIGNATURE_ALG);
mac.init(secretKey);
return new String(Base64.encode(mac.doFinal(baseString.getBytes()), Base64.DEFAULT)).trim();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {

View File

@ -26,7 +26,6 @@ public class Tokens {
private static Tokens instance;
private GlobalSettings settings;
private String token, tokenSec;
private Tokens(Context context) {
@ -66,33 +65,4 @@ public class Tokens {
return settings.getConsumerSecret();
return API_SECRET;
}
/**
* set oauth tokens
*
* @param tokens oauth tokens (single or pair)
*/
public void setTokens(String... tokens) {
if (tokens.length == 2) {
token = tokens[0];
tokenSec = tokens[1];
settings.setConnection(token, tokenSec);
} else if (tokens.length == 1) {
token = tokens[0];
}
}
/**
* @return first oauth token
*/
public String getToken() {
return token;
}
/**
* @return second secret oauth token
*/
public String getTokenSec() {
return tokenSec;
}
}

View File

@ -55,10 +55,16 @@ class AccountDB implements Account {
}
@Override
public String[] getKeys() {
return new String[]{key1, key2};
public String getAccessToken() {
return key1;
}
@Override
public String getTokenSecret() {
return key2;
}
@NonNull
@Override
public String toString() {

View File

@ -847,16 +847,58 @@ public class GlobalSettings {
return loggedIn;
}
/**
* get Access tokens
* set app login status
*
* @return access tokens
* @param login true if current user is logged in successfully
*/
public String[] getCurrentUserAccessToken() {
String[] out = new String[2];
out[0] = auth_key1;
out[1] = auth_key2;
return out;
public void setogin(boolean login) {
loggedIn = login;
Editor e = settings.edit();
e.putBoolean(LOGGED_IN, login);
e.apply();
}
/**
* return access token of the current user
*
* @return first access token
*/
public String getAccessToken() {
return auth_key1;
}
/**
* set access token of the current user
*
* @param token first access token
*/
public void setAccessToken(String token) {
this.auth_key1 = token;
Editor e = settings.edit();
e.putString(CURRENT_AUTH_KEY1, token);
e.apply();
}
/**
* return second access token of the current user
* @return first access token
*/
public String getTokenSecret() {
return auth_key2;
}
/**
* set second access token of the current user
*
* @param token first access token
*/
public void setTokenSecret(String token) {
this.auth_key2 = token;
Editor e = settings.edit();
e.putString(CURRENT_AUTH_KEY2, token);
e.apply();
}
/**
@ -887,28 +929,10 @@ public class GlobalSettings {
}
/**
* Set Access tokens and user ID
* set current user ID
*
* @param key1 1st access token
* @param key2 2nd access token
* @param userId User ID
* @param userId current user ID
*/
public void setConnection(String key1, String key2, long userId) {
setConnection(key1, key2);
setUserId(userId);
}
public void setConnection(String key1, String key2) {
this.auth_key1 = key1;
this.auth_key2 = key2;
loggedIn = true;
Editor e = settings.edit();
e.putString(CURRENT_AUTH_KEY1, key1);
e.putString(CURRENT_AUTH_KEY2, key2);
e.putBoolean(LOGGED_IN, true);
e.apply();
}
public void setUserId(long userId) {
this.userId = userId;
Editor e = settings.edit();

View File

@ -85,8 +85,9 @@ public class AccountFragment extends ListFragment implements OnAccountClickListe
@Override
public void onAccountClick(Account account) {
// set new account
String[] token = account.getKeys();
settings.setConnection(token[0], token[1], account.getId());
settings.setAccessToken(account.getAccessToken());
settings.setTokenSecret(account.getTokenSecret());
settings.setUserId(account.getId());
// finish activity and return to parent activity
requireActivity().setResult(RET_ACCOUNT_CHANGE);
requireActivity().finish();

View File

@ -26,7 +26,12 @@ public interface Account {
User getUser();
/**
* @return oauth keys
* @return first access token of the user
*/
String[] getKeys();
String getAccessToken();
/**
* @return second access token of the user
*/
String getTokenSecret();
}