Upgrade twitter4j (#16828)

Closes https://github.com/keycloak/keycloak/issues/16731
This commit is contained in:
rmartinc 2023-02-03 15:28:37 +01:00 committed by GitHub
parent 0e374c7a45
commit f8f112d8d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 89 additions and 40 deletions

View file

@ -141,7 +141,7 @@
<pax.web.version>7.1.0</pax.web.version> <pax.web.version>7.1.0</pax.web.version>
<servlet.api.30.version>1.0.2.Final</servlet.api.30.version> <servlet.api.30.version>1.0.2.Final</servlet.api.30.version>
<servlet.api.40.version>2.0.0.Final</servlet.api.40.version> <servlet.api.40.version>2.0.0.Final</servlet.api.40.version>
<twitter4j.version>4.0.7</twitter4j.version> <twitter4j.version>4.1.2</twitter4j.version>
<jna.version>4.1.0</jna.version> <jna.version>4.1.0</jna.version>
<!-- Databases --> <!-- Databases -->

View file

@ -28,6 +28,7 @@ import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.util.IdentityBrokerState; import org.keycloak.broker.provider.util.IdentityBrokerState;
import org.keycloak.broker.social.SocialIdentityProvider; import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Base64;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
@ -43,12 +44,14 @@ import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.vault.VaultStringSecret; import org.keycloak.vault.VaultStringSecret;
import twitter4j.AccessToken;
import twitter4j.OAuthAuthorization;
import twitter4j.RequestToken;
import twitter4j.Twitter; import twitter4j.Twitter;
import twitter4j.TwitterFactory; import twitter4j.v1.User;
import twitter4j.auth.AccessToken;
import twitter4j.auth.RequestToken;
import twitter4j.conf.ConfigurationBuilder;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
@ -67,14 +70,19 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
String TWITTER_TOKEN_TYPE="twitter"; String TWITTER_TOKEN_TYPE="twitter";
protected static final Logger logger = Logger.getLogger(TwitterIdentityProvider.class); protected static final Logger logger = Logger.getLogger(TwitterIdentityProvider.class);
private static final String TWITTER_TOKEN = "twitter_token"; private static final String TWITTER_TOKEN = "twitter_token";
private static final String TWITTER_TOKENSECRET = "twitter_tokenSecret";
private final OAuthAuthorization oAuthAuthorization;
public TwitterIdentityProvider(KeycloakSession session, OAuth2IdentityProviderConfig config) { public TwitterIdentityProvider(KeycloakSession session, OAuth2IdentityProviderConfig config) {
super(session, config); super(session, config);
try (VaultStringSecret vaultStringSecret = session.vault().getStringSecret(getConfig().getClientSecret())) {
oAuthAuthorization = OAuthAuthorization.newBuilder()
.oAuthConsumer(getConfig().getClientId(), vaultStringSecret.get().orElse(getConfig().getClientSecret()))
.build();
}
} }
@Override @Override
@ -84,17 +92,12 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
@Override @Override
public Response performLogin(AuthenticationRequest request) { public Response performLogin(AuthenticationRequest request) {
try (VaultStringSecret vaultStringSecret = session.vault().getStringSecret(getConfig().getClientSecret())) { try {
Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(getConfig().getClientId(), vaultStringSecret.get().orElse(getConfig().getClientSecret()));
URI uri = new URI(request.getRedirectUri() + "?state=" + request.getState().getEncoded()); URI uri = new URI(request.getRedirectUri() + "?state=" + request.getState().getEncoded());
RequestToken requestToken = oAuthAuthorization.getOAuthRequestToken(uri.toString());
RequestToken requestToken = twitter.getOAuthRequestToken(uri.toString());
AuthenticationSessionModel authSession = request.getAuthenticationSession(); AuthenticationSessionModel authSession = request.getAuthenticationSession();
authSession.setAuthNote(TWITTER_TOKEN, requestToken.getToken()); authSession.setAuthNote(TWITTER_TOKEN, Base64.encodeObject(requestToken));
authSession.setAuthNote(TWITTER_TOKENSECRET, requestToken.getTokenSecret());
URI authenticationUrl = URI.create(requestToken.getAuthenticationURL()); URI authenticationUrl = URI.create(requestToken.getAuthenticationURL());
@ -205,16 +208,19 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
OAuth2IdentityProviderConfig providerConfig = provider.getConfig(); OAuth2IdentityProviderConfig providerConfig = provider.getConfig();
try (VaultStringSecret vaultStringSecret = session.vault().getStringSecret(providerConfig.getClientSecret())) { try (VaultStringSecret vaultStringSecret = session.vault().getStringSecret(providerConfig.getClientSecret())) {
Twitter twitter = new TwitterFactory(new ConfigurationBuilder().setIncludeEmailEnabled(true).build()).getInstance();
twitter.setOAuthConsumer(providerConfig.getClientId(), vaultStringSecret.get().orElse(providerConfig.getClientSecret()));
String twitterToken = authSession.getAuthNote(TWITTER_TOKEN); String twitterToken = authSession.getAuthNote(TWITTER_TOKEN);
String twitterSecret = authSession.getAuthNote(TWITTER_TOKENSECRET); RequestToken requestToken;
try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(Base64.decode(twitterToken)))) {
requestToken = (RequestToken) in.readObject();
}
RequestToken requestToken = new RequestToken(twitterToken, twitterSecret); AccessToken oAuthAccessToken = provider.oAuthAuthorization.getOAuthAccessToken(requestToken, verifier);
AccessToken oAuthAccessToken = twitter.getOAuthAccessToken(requestToken, verifier); Twitter twitter = Twitter.newBuilder()
twitter4j.User twitterUser = twitter.verifyCredentials(); .oAuthConsumer(providerConfig.getClientId(), vaultStringSecret.get().orElse(providerConfig.getClientSecret()))
.oAuthAccessToken(oAuthAccessToken)
.build();
User twitterUser = twitter.v1().users().verifyCredentials();
BrokeredIdentityContext identity = new BrokeredIdentityContext(Long.toString(twitterUser.getId())); BrokeredIdentityContext identity = new BrokeredIdentityContext(Long.toString(twitterUser.getId()));
identity.setIdp(provider); identity.setIdp(provider);

View file

@ -0,0 +1,43 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.pages.social;
import org.jboss.arquillian.graphene.page.Page;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public class TwitterConsentLoginPage extends AbstractSocialLoginPage {
@FindBy(xpath = "//input[@type='submit' and @id='allow']")
private WebElement signInButton;
@Page
TwitterLoginPage loginPage;
@Override
public void login(String user, String password) {
// twitter presents a consent page for the application
// the SignIn button should be clicked first to go to the real login
signInButton.click();
loginPage.login(user, password);
}
}

View file

@ -17,7 +17,8 @@
package org.keycloak.testsuite.pages.social; package org.keycloak.testsuite.pages.social;
import org.openqa.selenium.NoSuchElementException; import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
@ -25,26 +26,25 @@ import org.openqa.selenium.support.FindBy;
* @author Vaclav Muzikar <vmuzikar@redhat.com> * @author Vaclav Muzikar <vmuzikar@redhat.com>
*/ */
public class TwitterLoginPage extends AbstractSocialLoginPage { public class TwitterLoginPage extends AbstractSocialLoginPage {
@FindBy(id = "username_or_email")
@FindBy(xpath = "//input[@type='text' and @name='text']")
private WebElement usernameInput; private WebElement usernameInput;
@FindBy(id = "password") @FindBy(xpath = "//input[@type='password']")
private WebElement passwordInput; private WebElement passwordInput;
@FindBy(id = "allow")
private WebElement loginButton;
@Override @Override
public void login(String user, String password) { public void login(String user, String password) {
try { // new login page is two phase login (username and then password) and it
usernameInput.clear(); // needs lots of JS, twitter does not work with default HtmlUnit driver
usernameInput.sendKeys(user); usernameInput.clear();
passwordInput.sendKeys(password); usernameInput.sendKeys(user);
} usernameInput.sendKeys(Keys.RETURN);
catch (NoSuchElementException e) { // at some conditions we are already logged in and just need to confirm it
} // wait for the password input to appear
finally { WaitUtils.waitUntilElement(passwordInput).is().visible();
loginButton.click(); passwordInput.clear();
} passwordInput.sendKeys(password);
passwordInput.sendKeys(Keys.RETURN);
} }
} }

View file

@ -46,7 +46,7 @@ import org.keycloak.testsuite.pages.social.MicrosoftLoginPage;
import org.keycloak.testsuite.pages.social.OpenShiftLoginPage; import org.keycloak.testsuite.pages.social.OpenShiftLoginPage;
import org.keycloak.testsuite.pages.social.PayPalLoginPage; import org.keycloak.testsuite.pages.social.PayPalLoginPage;
import org.keycloak.testsuite.pages.social.StackOverflowLoginPage; import org.keycloak.testsuite.pages.social.StackOverflowLoginPage;
import org.keycloak.testsuite.pages.social.TwitterLoginPage; import org.keycloak.testsuite.pages.social.TwitterConsentLoginPage;
import org.keycloak.testsuite.util.IdentityProviderBuilder; import org.keycloak.testsuite.util.IdentityProviderBuilder;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
@ -121,7 +121,7 @@ public class SocialLoginTest extends AbstractKeycloakTest {
FACEBOOK_INCLUDE_BIRTHDAY("facebook", FacebookLoginPage.class), FACEBOOK_INCLUDE_BIRTHDAY("facebook", FacebookLoginPage.class),
GITHUB("github", GitHubLoginPage.class), GITHUB("github", GitHubLoginPage.class),
GITHUB_PRIVATE_EMAIL("github", "github-private-email", GitHubLoginPage.class), GITHUB_PRIVATE_EMAIL("github", "github-private-email", GitHubLoginPage.class),
TWITTER("twitter", TwitterLoginPage.class), TWITTER("twitter", TwitterConsentLoginPage.class),
LINKEDIN("linkedin", LinkedInLoginPage.class), LINKEDIN("linkedin", LinkedInLoginPage.class),
LINKEDIN_WITH_PROJECTION("linkedin", LinkedInLoginPage.class), LINKEDIN_WITH_PROJECTION("linkedin", LinkedInLoginPage.class),
MICROSOFT("microsoft", MicrosoftLoginPage.class), MICROSOFT("microsoft", MicrosoftLoginPage.class),