Skip to content
This repository has been archived by the owner on Feb 10, 2021. It is now read-only.

"Remember Me" functionality #363

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
087db40
Brought back the scheleton implementation found here: https://github.…
bravegag Aug 12, 2018
d4c477a
remember me [beta]
alexeysavenkov Aug 14, 2018
965447d
isAuthorizedWithCookie() check
alexeysavenkov Aug 14, 2018
283772a
trying to authorize with cookie for EVERY non-logged-in request
alexeysavenkov Aug 14, 2018
676d421
separate relogin (re-type password) page
alexeysavenkov Aug 14, 2018
9fd8752
delete series on potential theft & on logout
alexeysavenkov Aug 14, 2018
b7abdc2
old tokens are now deleted on signout & relogin
alexeysavenkov Aug 14, 2018
d3f82af
Cookie check in action creator
alexeysavenkov Aug 15, 2018
500ef09
partial identation & formatting revert
alexeysavenkov Aug 15, 2018
cbfe27b
partial formatting revert
alexeysavenkov Aug 15, 2018
3bd028d
partial identation fix
alexeysavenkov Aug 15, 2018
83e362c
identation fix
alexeysavenkov Aug 15, 2018
221ae95
ident fix
alexeysavenkov Aug 15, 2018
eb2028d
ident fix
alexeysavenkov Aug 15, 2018
d051ba2
identation fix
alexeysavenkov Aug 15, 2018
de7fe01
reverted remember me to form
alexeysavenkov Aug 15, 2018
d94387b
fixed login form
alexeysavenkov Aug 15, 2018
c97b3fe
formatting
alexeysavenkov Aug 15, 2018
7db9f1c
indent fix
alexeysavenkov Aug 15, 2018
1a8e471
ident fix
alexeysavenkov Aug 15, 2018
52108fb
Sudo action with annotation & isLoggedIn fix
alexeysavenkov Aug 15, 2018
ae5c84a
isLoggedIn usage fix
alexeysavenkov Aug 15, 2018
f339f0f
isLoggedIn usage fix
alexeysavenkov Aug 15, 2018
fb08a0d
Adapting to the new "Remember Me" functionality.
bravegag Aug 15, 2018
d9d4435
Adapting to the new Remember Me functionality
bravegag Aug 15, 2018
791a0bc
Core PA merge completed
bravegag May 4, 2019
547d2b9
Usage sample migration completed
bravegag May 4, 2019
ab61d7b
Migration completed
bravegag May 4, 2019
570c3ed
Migration completed
bravegag May 4, 2019
b030512
Merge pull request #2 from bravegag/migrate-play-2.6
bravegag May 4, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
language: scala
sudo: true
sudo: required
dist: trusty
scala:
- 2.11.8
- 2.11.12
- 2.12.6
jdk:
- oraclejdk8
install: true
before_install:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
# See https://github.com/SeleniumHQ/docker-selenium/issues/87
- export DBUS_SESSION_BUS_ADDRESS=/dev/null
- export $(dbus-launch)
- export NSS_USE_SHARED_DB=ENABLED
- "export DISPLAY=:99.0"
- "Xvfb :99 +extension RANDR 2>/dev/null &"
- sleep 5 # give xvfb some time to start
- "mkdir -p ~/tmp/bin"
- "pushd ~/tmp/bin && wget -c https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux64.tar.gz && tar -xzvf geckodriver-v0.21.0-linux64.tar.gz && popd"
- "chmod -vv +x ~/tmp/bin/*"
- "export PATH=$HOME/tmp/bin:$PATH"
- wget -qO- https://toolbelt.heroku.com/install-ubuntu.sh | sh
script:
- cd $TRAVIS_BUILD_DIR/code
Expand All @@ -25,6 +36,12 @@ script:
- sbt ++$TRAVIS_SCALA_VERSION test < /dev/null
notifications:
email: false
addons:
firefox: latest
apt:
packages:
- unzip
- dbus-x11
env:
global:
# SONATYPE_USERNAME
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ This plugin uses concepts from [securesocial2][] and [Play20StartApp][] and prov

Play Authenticate is cross-tested in Java 1.6, Java 1.7 (Up to `0.6.x`) and Java 1.8 (from `0.7.0`)

Since Play 2.6 it's cross-compiled for both Scala 2.11 and 2.12.

Works fine with Play version

* `2.0.2` to `2.0.x` (last: `0.2.3-SNAPSHOT` - [2.0.x branch](https://github.com/joscha/play-authenticate/tree/2.0.x))
* `2.1.0` to `2.1.x` (last: `0.3.6` - [2.1.x branch](https://github.com/joscha/play-authenticate/tree/2.1.x))
* `2.2.0` to `2.2.x` (last: `0.5.4` - [2.2.x branch](https://github.com/joscha/play-authenticate/tree/2.2.x))
* `2.3.0` to `2.3.x` (last: `0.6.9` - [2.3.x branch](https://github.com/joscha/play-authenticate/tree/2.3.x))
* `2.4.0` to `2.4.x` (last: `0.7.x` - [2.4.x branch](https://github.com/joscha/play-authenticate/tree/2.4.x))
* `2.5.0` to `2.5.x` (last: `0.8.x` - [master branch](https://github.com/joscha/play-authenticate/tree/master))
* `2.5.0` to `2.5.x` (last: `0.8.x` - [2.5.x branch](https://github.com/joscha/play-authenticate/tree/2.5.x))
* `2.6.0` to `2.6.x` (last: `0.9.x` - [master branch](https://github.com/joscha/play-authenticate/tree/master))

Releases are on [mvnrepository](http://mvnrepository.com/artifact/com.feth) and snapshots can be found on [sonatype](https://oss.sonatype.org/content/repositories/snapshots/com/feth/).

Expand All @@ -31,15 +34,15 @@ Play-Authenticate is available in [Maven Central](http://search.maven.org/#brows
```xml
<dependency>
<groupId>com.feth</groupId>
<artifactId>play-authenticate_2.11</artifactId>
<version>0.8.3</version>
<artifactId>play-authenticate</artifactId>
<version>0.9.0</version>
</dependency>
```
or

```scala
val appDependencies = Seq(
"com.feth" % "play-authenticate_2.11" % "0.8.3"
"com.feth" %% "play-authenticate" % "0.9.0"
)
```

Expand Down Expand Up @@ -95,6 +98,10 @@ There is also a [sample application using Play!Authenticate with MongoDB](https:
## Versions
* **TRUNK** [not released in the repository, yet]
* Fancy contributing something? :-)
* **0.9.0** [tbd]
* Upgrade to Play 2.6 (thanks @KadekM)
* ATTENTION: This is for Play 2.6 - if you have Play 2.5, use a `0.8.x` version.
* Turkish core (thanks @canoztokmak)
* **0.8.3** [2017-04-18]
* Chinese translation (thanks @frederick036)
* Fix for facebook refresh token
Expand Down
133 changes: 107 additions & 26 deletions code/app/com/feth/play/module/pa/PlayAuthenticate.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import com.feth.play.module.pa.exceptions.AuthException;
import com.feth.play.module.pa.providers.AuthProvider;
import com.feth.play.module.pa.providers.cookie.CookieAuthProvider;
import com.feth.play.module.pa.providers.cookie.CookieAuthUser;
import com.feth.play.module.pa.service.UserService;
import com.feth.play.module.pa.user.AuthUser;
import play.Configuration;
import com.typesafe.config.Config;
import play.Logger;
import play.i18n.Messages;
import play.cache.SyncCacheApi;
import play.i18n.Lang;
import play.i18n.MessagesApi;
import play.mvc.Call;
import play.mvc.Controller;
import play.mvc.Http;
Expand All @@ -16,7 +20,11 @@

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Arrays;
import java.util.Date;
import java.util.Optional;
import java.util.List;
import java.util.Locale;

@Singleton
public class PlayAuthenticate {
Expand All @@ -28,17 +36,24 @@ public class PlayAuthenticate {
private static final String SETTING_KEY_ACCOUNT_AUTO_LINK = "accountAutoLink";
private static final String SETTING_KEY_ACCOUNT_AUTO_MERGE = "accountAutoMerge";



private Configuration config;
private List<Lang> preferredLangs;
private Config config;

@Inject
public PlayAuthenticate(final Configuration config, final Resolver resolver) {
public PlayAuthenticate(final Config config, final Resolver resolver, final MessagesApi messagesApi, final SyncCacheApi cacheApi) {
this.config = config;
this.resolver = resolver;
this.messagesApi = messagesApi;
this.cacheApi = cacheApi;

Locale englishLocale = new Locale("en");
Lang englishLang = new Lang(englishLocale);
preferredLangs = Arrays.asList(englishLang);
}

private Resolver resolver;
private final MessagesApi messagesApi;
private final SyncCacheApi cacheApi;

public Resolver getResolver() {
return resolver;
Expand All @@ -53,7 +68,7 @@ public void setUserService(final UserService service) {
public UserService getUserService() {
if (userService == null) {
throw new RuntimeException(
Messages.get("playauthenticate.core.exception.no_user_service"));
messagesApi.preferred(preferredLangs).at("playauthenticate.core.exception.no_user_service"));
}
return userService;
}
Expand All @@ -64,7 +79,7 @@ public UserService getUserService() {
private static final String EXPIRES_KEY = "pa.u.exp";
private static final String SESSION_ID_KEY = "pa.s.id";

public Configuration getConfiguration() {
public Config getConfiguration() {
return config
.getConfig(SETTING_KEY_PLAY_AUTHENTICATE);
}
Expand Down Expand Up @@ -113,11 +128,13 @@ public void storeUser(final Session session, final AuthUser authUser) {
}
}

public boolean isLoggedIn(final Session session) {
public boolean isLoggedIn(final Context context) {
Session session = context.session();

boolean ret = session.containsKey(USER_KEY) // user is set
&& session.containsKey(PROVIDER_KEY); // provider is set
ret &= AuthProvider.Registry.hasProvider(session.get(PROVIDER_KEY)); // this
// provider
// provider
// is
// active
if (session.containsKey(EXPIRES_KEY)) {
Expand All @@ -128,10 +145,17 @@ public boolean isLoggedIn(final Session session) {
// expires after now
}
}

if(!ret) {
ret = tryAuthenticateWithCookie(context);
}

return ret;
}

public Result logout(final Session session) {
public Result logout(final Context context) {
Session session = context.session();

session.remove(USER_KEY);
session.remove(PROVIDER_KEY);
session.remove(EXPIRES_KEY);
Expand All @@ -140,6 +164,10 @@ public Result logout(final Session session) {
// cookie
session.remove(ORIGINAL_URL);

getCookieAuthProvider().ifPresent(cookieAuthProvider -> {
cookieAuthProvider.forget(context);
});

return Controller.redirect(getUrl(getResolver().afterLogout(),
SETTING_KEY_AFTER_LOGOUT_FALLBACK));
}
Expand Down Expand Up @@ -182,6 +210,37 @@ public AuthUser getUser(final Context context) {
return getUser(context.session());
}

public boolean tryAuthenticateWithCookie(final Context context) {
Optional<CookieAuthProvider> cookieAuthProvider = getCookieAuthProvider();

Optional<CookieAuthUser> cookieAuthUser =
cookieAuthProvider.flatMap(provider -> Optional.ofNullable(provider.authenticate(context)));

Optional<CookieAuthUser> user = Optional.empty();
if(cookieAuthUser.isPresent()) {
user = cookieAuthUser;
rememberUser(context, user.get());
}
return cookieAuthUser.isPresent();
}

private Optional<CookieAuthProvider> getCookieAuthProvider() {
return AuthProvider.Registry.getProviders().stream()
.filter(x -> x instanceof CookieAuthProvider)
.map(x -> (CookieAuthProvider)x)
.findAny();
}

public boolean isAuthorizedWithCookie(final Context context) {
AuthUser user = getUser(context.session());

return getCookieAuthProvider().map(cookieAuthProvider ->
user.getProvider().equals(cookieAuthProvider.getKey())
).orElse(false);
}



public boolean isAccountAutoMerge() {
return getConfiguration().getBoolean(SETTING_KEY_ACCOUNT_AUTO_MERGE);
}
Expand Down Expand Up @@ -211,14 +270,14 @@ private void storeUserInCache(final Session session,

public void storeInCache(final Session session, final String key,
final Object o) {
play.cache.Cache.set(getCacheKey(session, key), o);
cacheApi.set(getCacheKey(session, key), o);
}

public <T> T removeFromCache(final Session session, final String key) {
final T o = getFromCache(session, key);

final String k = getCacheKey(session, key);
play.cache.Cache.remove(k);
cacheApi.remove(k);
return o;
}

Expand All @@ -229,7 +288,7 @@ private String getCacheKey(final Session session, final String key) {

@SuppressWarnings("unchecked")
public <T> T getFromCache(final Session session, final String key) {
return (T) play.cache.Cache.get(getCacheKey(session, key));
return (T) cacheApi.get(getCacheKey(session, key));
}

private AuthUser getUserFromCache(final Session session,
Expand Down Expand Up @@ -290,9 +349,9 @@ private String getUrl(final Call c, final String settingFallback) {
} else {
// go to root instead, but log this
Logger.warn("Resolver did not contain information about where to go - redirecting to /");
final String afterAuthFallback = getConfiguration().getString(
settingFallback);
if (afterAuthFallback != null && !afterAuthFallback.equals("")) {
final String afterAuthFallback;
if (getConfiguration().hasPath(settingFallback) && !(afterAuthFallback = getConfiguration().getString(
settingFallback)).isEmpty()) {
return afterAuthFallback;
}
// Not even the config setting was there or valid...meh
Expand Down Expand Up @@ -356,19 +415,33 @@ private AuthUser signupUser(final AuthUser u, final Session session, final AuthP
final Object id = getUserService().save(u);
if (id == null) {
throw new AuthException(
Messages.get("playauthenticate.core.exception.signupuser_failed"));
messagesApi.preferred(preferredLangs).at("playauthenticate.core.exception.signupuser_failed"));
}
provider.afterSave(u, id, session);
return u;
}

private void rememberUser(final Context context, AuthUser authUser) {
getCookieAuthProvider().ifPresent(cookieAuthProvider -> {
cookieAuthProvider.remember(context, authUser);

context.session().put(PlayAuthenticate.USER_KEY, authUser.getId());
context.session().put(PlayAuthenticate.PROVIDER_KEY, authUser.getProvider());
if (authUser.expires() != AuthUser.NO_EXPIRATION) {
context.session().put(EXPIRES_KEY, Long.toString(authUser.expires()));
} else {
context.session().remove(EXPIRES_KEY);
}
});
}

public Result handleAuthentication(final String provider,
final Context context, final Object payload) {
final Context context, final Object payload, boolean rememberMe) {
final AuthProvider ap = getProvider(provider);
if (ap == null) {
// Provider wasn't found and/or user was fooling with our stuff -
// tell him off:
return Controller.notFound(Messages.get(
return Controller.notFound(messagesApi.preferred(preferredLangs).at(
"playauthenticate.core.exception.provider_not_found",
provider));
}
Expand Down Expand Up @@ -402,7 +475,7 @@ public Result handleAuthentication(final String provider,
AuthUser oldUser = getUser(session);

// checks if the user is logged in (also checks the expiration!)
boolean isLoggedIn = isLoggedIn(session);
boolean isLoggedIn = isLoggedIn(context);

Object oldIdentity = null;

Expand All @@ -416,8 +489,12 @@ public Result handleAuthentication(final String provider,
// if isLoggedIn is false here, then the local user has
// been deleted/deactivated
// so kill the session
logout(session);
logout(context);
oldUser = null;
} else if(oldUser.getProvider().equals(CookieAuthProvider.getProviderKey())) {
getCookieAuthProvider().ifPresent(cookieAuthProvider ->
cookieAuthProvider.forget(context)
);
}
}

Expand Down Expand Up @@ -452,7 +529,7 @@ public Result handleAuthentication(final String provider,
final Call c = getResolver().askMerge();
if (c == null) {
throw new RuntimeException(
Messages.get(
messagesApi.preferred(preferredLangs).at(
"playauthenticate.core.exception.merge.controller_undefined",
SETTING_KEY_ACCOUNT_AUTO_MERGE));
}
Expand Down Expand Up @@ -484,7 +561,7 @@ public Result handleAuthentication(final String provider,
final Call c = getResolver().askLink();
if (c == null) {
throw new RuntimeException(
Messages.get(
messagesApi.preferred(preferredLangs).at(
"playauthenticate.core.exception.link.controller_undefined",
SETTING_KEY_ACCOUNT_AUTO_LINK));
}
Expand All @@ -494,10 +571,14 @@ public Result handleAuthentication(final String provider,

}

if(rememberMe) {
rememberUser(context, loginUser);
}

return loginAndRedirect(context, loginUser);
} else {
return Controller.internalServerError(Messages
.get("playauthenticate.core.exception.general"));
return Controller.internalServerError(messagesApi
.preferred(preferredLangs).at("playauthenticate.core.exception.general"));
}
} catch (final AuthException e) {
final Call c = getResolver().onException(e);
Expand Down
7 changes: 7 additions & 0 deletions code/app/com/feth/play/module/pa/Resolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ public abstract class Resolver {
*/
public abstract Call login();

/**
* Route to "re-type password" page
*
* @return
*/
public abstract Call relogin();

/**
* Route to redirect to after authentication has been finished.
* Only used if no original URL was stored.
Expand Down
Loading