Skip to content

Commit

Permalink
remove login method in OAuthApiConnection & add serialize/deserialize…
Browse files Browse the repository at this point in the history
… functionality to OAuthApiConnection
  • Loading branch information
afkbrb committed May 25, 2020
1 parent f66e767 commit a60a474
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 67 deletions.
Expand Up @@ -115,6 +115,13 @@ public ApiConnection(String apiBaseUrl) {
this.apiBaseUrl = apiBaseUrl;
}

/**
* Getter for the apiBaseUrl.
*/
public String getApiBaseUrl() {
return apiBaseUrl;
}

/**
* Returns true if a user is logged in. This does not perform
* any request to the server: it just returns our own internal state.
Expand Down
Expand Up @@ -259,6 +259,12 @@ protected void confirmLogin(String token, String username, String password)
}
}

@Override
@JsonProperty("baseUrl")
public String getApiBaseUrl() {
return super.getApiBaseUrl();
}

@Override
@JsonProperty("loggedIn")
public boolean isLoggedIn() {
Expand Down
Expand Up @@ -21,8 +21,11 @@
*/


import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import okhttp3.*;
import org.wikidata.wdtk.wikibaseapi.apierrors.AssertUserFailedException;
import org.wikidata.wdtk.wikibaseapi.apierrors.MediaWikiApiErrorException;
import se.akerfeldt.okhttp.signpost.OkHttpOAuthConsumer;
import se.akerfeldt.okhttp.signpost.SigningInterceptor;
Expand All @@ -31,6 +34,7 @@
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
* A connection to the MediaWiki/Wikibase API which uses OAuth
Expand All @@ -41,62 +45,85 @@
*/
public class OAuthApiConnection extends ApiConnection {

protected OkHttpOAuthConsumer consumer;
private String consumerKey;
private String consumerSecret;

/**
* This client is used if the user hadn't logged in when sending a request.
*/
protected OkHttpClient client;
private String accessToken;
private String accessSecret;

/**
* This client is used if the user had already logged in when sending a request.
*/
protected OkHttpClient oauthClient;
private OkHttpClient client;

public static final MediaType MEDIA_TYPE = MediaType.parse("application/x-www-form-urlencoded");
private static final MediaType MEDIA_TYPE = MediaType.parse("application/x-www-form-urlencoded");

/**
* Constructs a connection to the given MediaWiki
* API endpoint.
* Constructs a plain connection without OAuth functionality to the given MediaWiki API endpoint.
* <p>
* Use this constructor if you will only make anonymous requests which don't require logging in.
* <p>
* {@link OAuthApiConnection#isLoggedIn()} will always return false if using this constructor.
*
* @param apiBaseUrl the MediaWiki API endpoint, such as "https://www.wikidata.org/w/api.php"
* @param consumerKey the OAuth 1.0a consumer key
* @param consumerSecret the OAuth 1.0a consumer secret
* @param apiBaseUrl the MediaWiki API endpoint, such as "https://www.wikidata.org/w/api.php"
*/
public OAuthApiConnection(String apiBaseUrl, String consumerKey, String consumerSecret) {
public OAuthApiConnection(String apiBaseUrl) {
super(apiBaseUrl);
consumer = new OkHttpOAuthConsumer(consumerKey, consumerSecret);
client = new OkHttpClient.Builder().build();
loggedIn = false;
}

/**
* Once an access token has been obtained via the OAuth login process,
* this registers the connection as logged in with this token.
* Constructs an OAuth connection to the given MediaWiki API endpoint.
* <p>
* {@link ApiConnection#isLoggedIn()} will return true
* if this constructor is used and {@link ApiConnection#logout()} hasn't been called.
* <p>
* NOTICE: The constructor doesn't check if the OAuth credentials
* (i.e., the consumer key/secret and the access token/secret) are valid.
* Even if the credentials are valid when calling this constructor,
* they can be revoked by the user at any time.
* <p>
* The validity of the credentials is automatically checked if you use
* {@link ApiConnection#sendJsonRequest}.
*
* @param accessToken the access token obtained after the OAuth process
* @param accessSecret the secret key obtained after the OAuth process
* @throws LoginFailedException if the access token/secret are not valid
* @param apiBaseUrl the MediaWiki API endpoint, such as "https://www.wikidata.org/w/api.php"
* @param consumerKey the OAuth 1.0a consumer key
* @param consumerSecret the OAuth 1.0a consumer secret
* @param accessToken the access token obtained via the OAuth process
* @param accessSecret the secret key obtained via the OAuth process
*/
public void login(String accessToken, String accessSecret) throws LoginFailedException {
@JsonCreator
public OAuthApiConnection(
@JsonProperty("baseUrl") String apiBaseUrl,
@JsonProperty("consumerKey") String consumerKey,
@JsonProperty("consumerSecret") String consumerSecret,
@JsonProperty("accessToken") String accessToken,
@JsonProperty("accessSecret") String accessSecret) {
super(apiBaseUrl);
this.consumerKey = consumerKey;
this.consumerSecret = consumerSecret;
this.accessToken = accessToken;
this.accessSecret = accessSecret;
OkHttpOAuthConsumer consumer = new OkHttpOAuthConsumer(consumerKey, consumerSecret);
consumer.setTokenWithSecret(accessToken, accessSecret);
oauthClient = new OkHttpClient.Builder()
client = new OkHttpClient.Builder()
.addInterceptor(new SigningInterceptor(consumer))
.build();

// Try to get the user's name, if successful, we know that
// the consumer key/secret and access token/secret are valid.
Map<String, String> params = new HashMap<>();
params.put(PARAM_ACTION, "query");
params.put("meta", "userinfo");
try {
JsonNode root = sendJsonRequest("GET", params);
username = root.path("query").path("userinfo").path("name").textValue();
loggedIn = true;
} catch (IOException | MediaWikiApiErrorException e) {
throw new LoginFailedException(e.getMessage(), e);
}
loggedIn = true;
}

/**
* Sends a request to the API with the given parameters and the given
* request method and returns the result string.
* <p>
* WARNING: You probably want to use {@link ApiConnection#sendJsonRequest}
* that execute the request using JSON content format,
* throws the errors and logs the warnings.
*
* @param requestMethod either POST or GET
* @param parameters Maps parameter keys to values. Out of this map the function
* will create a query string for the request.
* @return API result
* @throws IOException
*/
@Override
public InputStream sendRequest(String requestMethod, Map<String, String> parameters) throws IOException {
Request request;
Expand All @@ -109,21 +136,98 @@ public InputStream sendRequest(String requestMethod, Map<String, String> paramet
throw new IllegalArgumentException("Expected the requestMethod to be either GET or POST, but got " + requestMethod);
}

Response response;
if (oauthClient == null) {
response = client.newCall(request).execute();
} else {
response = oauthClient.newCall(request).execute();
}

return response.body().byteStream();
Response response = client.newCall(request).execute();
return Objects.requireNonNull(response.body()).byteStream();
}

/**
* Forgets the OAuth credentials locally.
* No requests will be made.
*/
@Override
public void logout() throws IOException {
oauthClient = null;
public void logout() {
consumerKey = null;
consumerSecret = null;
accessToken = null;
accessSecret = null;
username = "";
loggedIn = false;
}

/**
* Checks if the OAuth credentials (i.e., consumer key/secret and access token/secret) are still valid.
* <p>
* The OAuth credentials can be invalid if the user invoked it.
* <p>
* We simply call {@link ApiConnection#checkCredentials()} here.
* Because for OAuth, the query "action=query&assert=user" returns success
* if and only if the credentials are still valid. This behaviour is the
* same when using username/password for logging in.
* <p>
* This method throws {@link AssertUserFailedException} if the check failed.
* This does not update the state of the connection object.
*
* @throws MediaWikiApiErrorException if the check failed
* @throws IOException
*/
@Override
public void checkCredentials() throws IOException, MediaWikiApiErrorException {
super.checkCredentials();
}

@Override
@JsonProperty("baseUrl")
public String getApiBaseUrl() {
return super.getApiBaseUrl();
}

@Override
@JsonProperty("loggedIn")
public boolean isLoggedIn() {
return super.isLoggedIn();
}

@Override
@JsonProperty("username")
public String getCurrentUser() {
if (!loggedIn) return "";
if (username != null && !username.equals("")) return username;

try {
Map<String, String> params = new HashMap<>();
params.put(PARAM_ACTION, "query");
params.put("meta", "userinfo");
JsonNode root = sendJsonRequest("GET", params);
JsonNode nameNode = root.path("query").path("userinfo").path("name");
if (nameNode.isMissingNode()) {
throw new AssertUserFailedException("The path \"query/userinfo/name\" doesn't exist in the json response");
}
username = nameNode.textValue();
} catch (IOException | MediaWikiApiErrorException e) {
logger.warn("An error occurred when retrieving the username with OAuth credentials, the username is set to \"\" automatically: " + e.getMessage());
username = "";
}
return username;
}

@JsonProperty("consumerKey")
public String getConsumerKey() {
return consumerKey;
}

@JsonProperty("consumerSecret")
public String getConsumerSecret() {
return consumerSecret;
}

@JsonProperty("accessToken")
public String getAccessToken() {
return accessToken;
}

@JsonProperty("accessSecret")
public String getAccessSecret() {
return accessSecret;
}

}
Expand Up @@ -54,11 +54,7 @@ public class BasicApiConnectionTest {

MockBasicApiConnection con;

private String LOGGED_IN_SERIALIZED_CONNECTION = "{\"loggedIn\":true,\"cookies\":{\"Path\":\"/\","+
"\"GeoIP\":\"DE:13:Dresden:51.0500:13.7500:v4\",\" path\":\"/\",\" Domain\":\".wikidata.org\","+
"\"testwikidatawikiSession\":\"c18ef92637227283bcda73bcf95cfaf5\",\" secure\":\"\","+
"\"WMF-Last-Access\":\"18-Aug-2015\",\"Expires\":\"Sat, 19 Sep 2015 12:00:00 GMT\",\"HttpOnly\":\"\","+
"\" Path\":\"/\",\" httponly\":\"\"},\"username\":\"username\"}";
private String LOGGED_IN_SERIALIZED_CONNECTION = "{\"baseUrl\":\"https://mocked.api.connection/w/api.php\",\"loggedIn\":true,\"cookies\":{\"Path\":\"/\",\"GeoIP\":\"DE:13:Dresden:51.0500:13.7500:v4\",\" path\":\"/\",\" Domain\":\".wikidata.org\",\"testwikidatawikiSession\":\"c18ef92637227283bcda73bcf95cfaf5\",\" secure\":\"\",\"WMF-Last-Access\":\"18-Aug-2015\",\"Expires\":\"Sat, 19 Sep 2015 12:00:00 GMT\",\"HttpOnly\":\"\",\" Path\":\"/\",\" httponly\":\"\"},\"username\":\"username\"}";

Set<String> split(String str, char ch) {
Set<String> set = new TreeSet<String>();
Expand Down Expand Up @@ -181,6 +177,7 @@ public void testDeserialize() throws IOException {
ApiConnection newConn = mapper.readValue(LOGGED_IN_SERIALIZED_CONNECTION, BasicApiConnection.class);
assertTrue(newConn.isLoggedIn());
assertEquals("username", newConn.getCurrentUser());
assertEquals("https://mocked.api.connection/w/api.php", newConn.getApiBaseUrl());
}

@Test
Expand Down
Expand Up @@ -53,7 +53,7 @@ public class MockBasicApiConnection extends BasicApiConnection {
* Constructor.
*/
public MockBasicApiConnection() {
super("");
super("https://mocked.api.connection/w/api.php");
webResources = new HashMap<Integer, byte[]>();
}

Expand Down

0 comments on commit a60a474

Please sign in to comment.