├── project
├── build.properties
└── plugins.sbt
├── Procfile
├── public
├── images
│ ├── favicon.png
│ ├── silhouette.png
│ └── providers
│ │ ├── vk.png
│ │ ├── xing.png
│ │ ├── google.png
│ │ ├── yahoo.png
│ │ ├── facebook.png
│ │ └── twitter.png
└── styles
│ └── main.css
├── .gitignore
├── app
├── models
│ ├── daos
│ │ ├── DAOSlick.scala
│ │ ├── UserDAO.scala
│ │ ├── UserDAOImpl.scala
│ │ ├── OAuth1InfoDAO.scala
│ │ ├── PasswordInfoDAO.scala
│ │ ├── OAuth2InfoDAO.scala
│ │ ├── OpenIDInfoDAO.scala
│ │ └── DBTableDefinitions.scala
│ ├── User.scala
│ └── services
│ │ ├── UserService.scala
│ │ └── UserServiceImpl.scala
├── utils
│ ├── Filters.scala
│ └── ErrorHandler.scala
├── forms
│ ├── SignInForm.scala
│ └── SignUpForm.scala
├── views
│ ├── home.scala.html
│ ├── signUp.scala.html
│ ├── signIn.scala.html
│ └── main.scala.html
├── controllers
│ ├── ApplicationController.scala
│ ├── SocialAuthController.scala
│ ├── SignUpController.scala
│ └── CredentialsAuthController.scala
└── modules
│ └── SilhouetteModule.scala
├── activator.properties
├── scripts
├── reformat
└── sbt
├── app.json
├── conf
├── routes
├── messages
├── evolutions
│ └── default
│ │ └── 1.sql
├── application.prod.conf
├── application.conf
└── silhouette.conf
├── README.md
├── test
└── controllers
│ └── ApplicationControllerSpec.scala
└── LICENSE
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.8
2 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: target/universal/stage/bin/play-silhouette-seed -Dhttp.port=${PORT} -Dconfig.resource=${PLAY_CONF_FILE}
2 |
--------------------------------------------------------------------------------
/public/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbrunk/play-silhouette-slick-seed/HEAD/public/images/favicon.png
--------------------------------------------------------------------------------
/public/images/silhouette.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbrunk/play-silhouette-slick-seed/HEAD/public/images/silhouette.png
--------------------------------------------------------------------------------
/public/images/providers/vk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbrunk/play-silhouette-slick-seed/HEAD/public/images/providers/vk.png
--------------------------------------------------------------------------------
/public/images/providers/xing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbrunk/play-silhouette-slick-seed/HEAD/public/images/providers/xing.png
--------------------------------------------------------------------------------
/public/images/providers/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbrunk/play-silhouette-slick-seed/HEAD/public/images/providers/google.png
--------------------------------------------------------------------------------
/public/images/providers/yahoo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbrunk/play-silhouette-slick-seed/HEAD/public/images/providers/yahoo.png
--------------------------------------------------------------------------------
/public/images/providers/facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbrunk/play-silhouette-slick-seed/HEAD/public/images/providers/facebook.png
--------------------------------------------------------------------------------
/public/images/providers/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sbrunk/play-silhouette-slick-seed/HEAD/public/images/providers/twitter.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | project/project
3 | project/target
4 | target
5 | tmp
6 | .history
7 | dist
8 | /.idea
9 | /*.iml
10 | /out
11 | /.idea_modules
12 | /.classpath
13 | /.project
14 | /RUNNING_PID
15 | /.settings
16 | /bin
17 | /.cache-main
18 | /.cache-tests
19 |
--------------------------------------------------------------------------------
/app/models/daos/DAOSlick.scala:
--------------------------------------------------------------------------------
1 | package models.daos
2 |
3 | import slick.driver.JdbcProfile
4 | import play.api.db.slick.HasDatabaseConfigProvider
5 |
6 | /**
7 | * Trait that contains generic slick db handling code to be mixed in with DAOs
8 | */
9 | trait DAOSlick extends DBTableDefinitions with HasDatabaseConfigProvider[JdbcProfile]
--------------------------------------------------------------------------------
/app/utils/Filters.scala:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import javax.inject.Inject
4 |
5 | import play.api.http.HttpFilters
6 | import play.api.mvc.EssentialFilter
7 | import play.filters.csrf.CSRFFilter
8 | import play.filters.headers.SecurityHeadersFilter
9 |
10 | /**
11 | * Provides filters.
12 | */
13 | class Filters @Inject() (csrfFilter: CSRFFilter, securityHeadersFilter: SecurityHeadersFilter) extends HttpFilters {
14 | override def filters: Seq[EssentialFilter] = Seq(csrfFilter, securityHeadersFilter)
15 | }
16 |
--------------------------------------------------------------------------------
/activator.properties:
--------------------------------------------------------------------------------
1 | # http://typesafe.com/activator/template/contribute
2 | name = play-silhouette-slick-seed
3 | title = The Play Silhouette Slick Seed Project
4 | description = A template for an application which uses Silhouette 3(http://silhouette.mohiva.com/) as authentication library. It uses Slick 3 with asynchronous database I/O and the play-slick plugin to store authentication information in a database.
5 | tags = seed, auth, oauth1, oauth2, openid, credentials, silhouette, authentication, scala, play, slick, database
6 | authorName = Sören Brunk
7 | authorLink = https://github.com/sbrunk
8 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | // Comment to get more information during initialization
2 | logLevel := Level.Warn
3 |
4 | // The Typesafe repository
5 | resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
6 |
7 | // The Sonatype snapshots repository
8 | resolvers += "Sonatype snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
9 |
10 | // Use the Play sbt plugin for Play projects
11 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.2")
12 |
13 | // Use the Scalariform plugin to reformat the code
14 | addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.3.0")
15 |
--------------------------------------------------------------------------------
/app/forms/SignInForm.scala:
--------------------------------------------------------------------------------
1 | package forms
2 |
3 | import play.api.data.Form
4 | import play.api.data.Forms._
5 |
6 | /**
7 | * The form which handles the submission of the credentials.
8 | */
9 | object SignInForm {
10 |
11 | /**
12 | * A play framework form.
13 | */
14 | val form = Form(
15 | mapping(
16 | "email" -> email,
17 | "password" -> nonEmptyText,
18 | "rememberMe" -> boolean
19 | )(Data.apply)(Data.unapply)
20 | )
21 |
22 | /**
23 | * The form data.
24 | *
25 | * @param email The email of the user.
26 | * @param password The password of the user.
27 | * @param rememberMe Indicates if the user should stay logged in on the next visit.
28 | */
29 | case class Data(
30 | email: String,
31 | password: String,
32 | rememberMe: Boolean)
33 | }
34 |
--------------------------------------------------------------------------------
/scripts/reformat:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Reformats source code.
4 | #
5 | # Copyright 2015 Mohiva Organisation (license at mohiva dot com)
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | #
19 | set -o nounset -o errexit
20 |
21 | scripts/sbt scalariformFormat test:scalariformFormat
22 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "play-silhouette-slick-seed",
3 | "description": "Seed project to show how Silhouette can be implemented into a Play Framework application with database access using Slick 3.",
4 | "keywords": [
5 | "Play",
6 | "Silhouette",
7 | "Slick"
8 | ],
9 | "website": "https://github.com/sbrunk/play-silhouette-slick-seed",
10 | "repository": "https://github.com/sbrunk/play-silhouette-slick-seed",
11 | "success_url": "/",
12 | "env": {
13 | "BUILDPACK_URL": "https://github.com/heroku/heroku-buildpack-scala.git",
14 | "PLAY_CONF_FILE": "application.prod.conf",
15 | "PLAY_APP_SECRET": "changeme",
16 | "FACEBOOK_CLIENT_ID": "",
17 | "FACEBOOK_CLIENT_SECRET": "",
18 | "GOOGLE_CLIENT_ID": "",
19 | "GOOGLE_CLIENT_SECRET": "",
20 | "TWITTER_CONSUMER_KEY": "",
21 | "TWITTER_CONSUMER_SECRET": ""
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/forms/SignUpForm.scala:
--------------------------------------------------------------------------------
1 | package forms
2 |
3 | import play.api.data.Form
4 | import play.api.data.Forms._
5 |
6 | /**
7 | * The form which handles the sign up process.
8 | */
9 | object SignUpForm {
10 |
11 | /**
12 | * A play framework form.
13 | */
14 | val form = Form(
15 | mapping(
16 | "firstName" -> nonEmptyText,
17 | "lastName" -> nonEmptyText,
18 | "email" -> email,
19 | "password" -> nonEmptyText
20 | )(Data.apply)(Data.unapply)
21 | )
22 |
23 | /**
24 | * The form data.
25 | *
26 | * @param firstName The first name of a user.
27 | * @param lastName The last name of a user.
28 | * @param email The email of the user.
29 | * @param password The password of the user.
30 | */
31 | case class Data(
32 | firstName: String,
33 | lastName: String,
34 | email: String,
35 | password: String)
36 | }
37 |
--------------------------------------------------------------------------------
/app/models/User.scala:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import java.util.UUID
4 |
5 | import com.mohiva.play.silhouette.api.{ Identity, LoginInfo }
6 |
7 | /**
8 | * The user object.
9 | *
10 | * @param userID The unique ID of the user.
11 | * @param loginInfo The linked login info.
12 | * @param firstName Maybe the first name of the authenticated user.
13 | * @param lastName Maybe the last name of the authenticated user.
14 | * @param fullName Maybe the full name of the authenticated user.
15 | * @param email Maybe the email of the authenticated provider.
16 | * @param avatarURL Maybe the avatar URL of the authenticated provider.
17 | */
18 | case class User(
19 | userID: UUID,
20 | loginInfo: LoginInfo,
21 | firstName: Option[String],
22 | lastName: Option[String],
23 | fullName: Option[String],
24 | email: Option[String],
25 | avatarURL: Option[String]) extends Identity
26 |
--------------------------------------------------------------------------------
/app/models/services/UserService.scala:
--------------------------------------------------------------------------------
1 | package models.services
2 |
3 | import com.mohiva.play.silhouette.api.services.IdentityService
4 | import com.mohiva.play.silhouette.impl.providers.CommonSocialProfile
5 | import models.User
6 |
7 | import scala.concurrent.Future
8 |
9 | /**
10 | * Handles actions to users.
11 | */
12 | trait UserService extends IdentityService[User] {
13 |
14 | /**
15 | * Saves a user.
16 | *
17 | * @param user The user to save.
18 | * @return The saved user.
19 | */
20 | def save(user: User): Future[User]
21 |
22 | /**
23 | * Saves the social profile for a user.
24 | *
25 | * If a user exists for this profile then update the user, otherwise create a new user with the given profile.
26 | *
27 | * @param profile The social profile to save.
28 | * @return The user for whom the profile was saved.
29 | */
30 | def save(profile: CommonSocialProfile): Future[User]
31 | }
32 |
--------------------------------------------------------------------------------
/app/models/daos/UserDAO.scala:
--------------------------------------------------------------------------------
1 | package models.daos
2 |
3 | import java.util.UUID
4 |
5 | import com.mohiva.play.silhouette.api.LoginInfo
6 | import models.User
7 |
8 | import scala.concurrent.Future
9 |
10 | /**
11 | * Give access to the user object.
12 | */
13 | trait UserDAO {
14 |
15 | /**
16 | * Finds a user by its login info.
17 | *
18 | * @param loginInfo The login info of the user to find.
19 | * @return The found user or None if no user for the given login info could be found.
20 | */
21 | def find(loginInfo: LoginInfo): Future[Option[User]]
22 |
23 | /**
24 | * Finds a user by its user ID.
25 | *
26 | * @param userID The ID of the user to find.
27 | * @return The found user or None if no user for the given ID could be found.
28 | */
29 | def find(userID: UUID): Future[Option[User]]
30 |
31 | /**
32 | * Saves a user.
33 | *
34 | * @param user The user to save.
35 | * @return The saved user.
36 | */
37 | def save(user: User): Future[User]
38 | }
39 |
--------------------------------------------------------------------------------
/conf/routes:
--------------------------------------------------------------------------------
1 | # Routes
2 | # This file defines all application routes (Higher priority routes first)
3 | # ~~~~
4 |
5 | # Home page
6 | GET / controllers.ApplicationController.index
7 | GET /signIn controllers.ApplicationController.signIn
8 | GET /signUp controllers.ApplicationController.signUp
9 | GET /signOut controllers.ApplicationController.signOut
10 | GET /authenticate/:provider controllers.SocialAuthController.authenticate(provider)
11 | POST /authenticate/credentials controllers.CredentialsAuthController.authenticate
12 | POST /signUp controllers.SignUpController.signUp
13 |
14 | # Map static resources from the /public folder to the /assets URL path
15 | GET /assets/*file controllers.Assets.at(path="/public", file)
16 | GET /webjars/*file controllers.WebJarAssets.at(file)
17 |
--------------------------------------------------------------------------------
/conf/messages:
--------------------------------------------------------------------------------
1 | error.email = Valid email required
2 | error.required = This field is required
3 | invalid.credentials = Invalid credentials!
4 | access.denied = Access denied!
5 | user.exists = There already exists a user with this email!
6 | could.not.authenticate = Could not authenticate with social provider! Please try again!
7 |
8 | home.title = Silhouette - Home
9 | sign.up.title = Silhouette - Sign Up
10 | sign.in.title = Silhouette - Sign In
11 |
12 | toggle.navigation = Toggle navigation
13 | welcome.signed.in = Welcome, you are now signed in!
14 |
15 | sign.up.account = Sign up for a new account
16 | sign.in.credentials = Sign in with your credentials
17 |
18 | error = Error!
19 | home = Home
20 | first.name = First name
21 | last.name = Last name
22 | full.name = Full name
23 | email = Email
24 | password = Password
25 | sign.up = Sign up
26 | sign.in = Sign in
27 | sign.out = Sign out
28 | sign.in.now = Sign in now
29 | sign.up.now = Sign up now
30 | already.a.member = Already a member?
31 | not.a.member = Not a member?
32 |
33 | remember.me = Remember my login on this computer
34 | or.use.social = Or use your existing account on one of the following services to sign in:
35 |
36 | google = Google
37 | facebook = Facebook
38 | twitter = Twitter
39 | vk = VK
40 | xing = Xing
41 | yahoo = Yahoo
42 |
--------------------------------------------------------------------------------
/conf/evolutions/default/1.sql:
--------------------------------------------------------------------------------
1 | # --- !Ups
2 |
3 | create table "user" ("userID" VARCHAR NOT NULL PRIMARY KEY,"firstName" VARCHAR,"lastName" VARCHAR,"fullName" VARCHAR,"email" VARCHAR,"avatarURL" VARCHAR);
4 | create table "logininfo" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,"providerID" VARCHAR NOT NULL,"providerKey" VARCHAR NOT NULL);
5 | create table "userlogininfo" ("userID" VARCHAR NOT NULL,"loginInfoId" BIGINT NOT NULL);
6 | create table "passwordinfo" ("hasher" VARCHAR NOT NULL,"password" VARCHAR NOT NULL,"salt" VARCHAR,"loginInfoId" BIGINT NOT NULL);
7 | create table "oauth1info" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,"token" VARCHAR NOT NULL,"secret" VARCHAR NOT NULL,"loginInfoId" BIGINT NOT NULL);
8 | create table "oauth2info" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,"accesstoken" VARCHAR NOT NULL,"tokentype" VARCHAR,"expiresin" INTEGER,"refreshtoken" VARCHAR,"logininfoid" BIGINT NOT NULL);
9 | create table "openidinfo" ("id" VARCHAR NOT NULL PRIMARY KEY,"logininfoid" BIGINT NOT NULL);
10 | create table "openidattributes" ("id" VARCHAR NOT NULL,"key" VARCHAR NOT NULL,"value" VARCHAR NOT NULL);
11 |
12 |
13 | # --- !Downs
14 |
15 | drop table "openidattributes";
16 | drop table "openidinfo";
17 | drop table "oauth2info";
18 | drop table "oauth1info";
19 | drop table "passwordinfo";
20 | drop table "userlogininfo";
21 | drop table "logininfo";
22 | drop table "user";
--------------------------------------------------------------------------------
/conf/application.prod.conf:
--------------------------------------------------------------------------------
1 | include "application.conf"
2 |
3 | play.crypto.secret=${?PLAY_APP_SECRET}
4 |
5 | silhouette {
6 |
7 | # Authenticator settings
8 | authenticator.cookieDomain="play-silhouette-seed.herokuapp.com"
9 | authenticator.secureCookie=true
10 |
11 | # OAuth1 token secret provider settings
12 | oauth1TokenSecretProvider.cookieDomain="play-silhouette-seed.herokuapp.com"
13 | oauth1TokenSecretProvider.secureCookie=true
14 |
15 | # OAuth2 state provider settings
16 | oauth2StateProvider.cookieDomain="play-silhouette-seed.herokuapp.com"
17 | oauth2StateProvider.secureCookie=true
18 |
19 | # Facebook provider
20 | facebook.redirectURL="https://play-silhouette-seed.herokuapp.com/authenticate/facebook"
21 |
22 | # Google provider
23 | google.redirectURL="https://play-silhouette-seed.herokuapp.com/authenticate/google"
24 |
25 | # VK provider
26 | vk.redirectURL="https://play-silhouette-seed.herokuapp.com/authenticate/vk"
27 |
28 | # Clef provider
29 | clef.redirectURL="https://play-silhouette-seed.herokuapp.com/authenticate/clef"
30 |
31 | # Twitter provider
32 | twitter.callbackURL="https://play-silhouette-seed.herokuapp.com/authenticate/twitter"
33 |
34 | # Xing provider
35 | xing.callbackURL="https://play-silhouette-seed.herokuapp.com/authenticate/xing"
36 |
37 | # Yahoo provider
38 | yahoo.callbackURL="https://play-silhouette-seed.herokuapp.com/authenticate/yahoo"
39 | yahoo.realm="https://play-silhouette-seed.herokuapp.com"
40 | }
41 |
--------------------------------------------------------------------------------
/app/views/home.scala.html:
--------------------------------------------------------------------------------
1 | @(user: models.User)(implicit messages: Messages)
2 |
3 | @main(Messages("home.title"), Some(user)) {
4 |
5 |
6 |
7 |
@Messages("welcome.signed.in")
8 |
9 |

10 |
11 |
12 |
13 |
14 |
15 |
16 |
@Messages("first.name"):
@user.firstName.getOrElse("None")
17 |
18 |
19 |
@Messages("last.name"):
@user.lastName.getOrElse("None")
20 |
21 |
22 |
@Messages("full.name"):
@user.fullName.getOrElse("None")
23 |
24 |
25 |
@Messages("email"):
@user.email.getOrElse("None")
26 |
27 |
28 |
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/public/styles/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 50px;
3 | }
4 |
5 | h1 {
6 | text-align: center;
7 | font-size: 30px;
8 | }
9 | .starter-template {
10 | padding: 40px 15px;
11 | }
12 |
13 | input, button {
14 | margin: 5px 0;
15 | }
16 |
17 | fieldset {
18 | margin-top: 100px;
19 | }
20 | legend {
21 | font-family: 'Montserrat', sans-serif;
22 | text-align: center;
23 | font-size: 20px;
24 | padding: 15px;
25 | }
26 | a {
27 | cursor: pointer;
28 | }
29 |
30 | .provider {
31 | display: inline-block;
32 | width: 64px;
33 | height: 64px;
34 | border-radius: 4px;
35 | outline: none;
36 | }
37 | .facebook { background: #3B5998; }
38 | .google { background: #D14836; }
39 | .twitter { background: #00ACED; }
40 | .yahoo { background: #731A8B; }
41 | .xing { background: #006567; }
42 | .vk { background: #567ca4; }
43 |
44 | .social-providers,
45 | .sign-in-now,
46 | .already-member,
47 | .not-a-member {
48 | text-align: center;
49 | margin-top: 20px;
50 | }
51 |
52 | .clef-button-wrapper {
53 | width: 190px!important;
54 | height: 34px!important;
55 | margin: 20px auto 0 auto!important;
56 | }
57 |
58 | .user {
59 | margin-top: 50px;
60 | }
61 | .user .data {
62 | margin-top: 10px;
63 | }
64 |
65 | .form-control {
66 | border-radius: 0;
67 | }
68 |
69 | [class^='ion-'] {
70 | font-size: 1.2em;
71 | }
72 |
73 | .has-feedback .form-control-feedback {
74 | top: 0;
75 | left: 0;
76 | width: 46px;
77 | height: 46px;
78 | line-height: 46px;
79 | color: #555;
80 | }
81 |
82 | .has-feedback .form-control {
83 | padding-left: 42.5px;
84 | }
85 |
86 | .btn {
87 | font-weight: bold;
88 | border-radius: 2px;
89 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .26);
90 | }
91 |
92 | .btn-lg {
93 | font-size: 18px;
94 | }
95 |
--------------------------------------------------------------------------------
/app/views/signUp.scala.html:
--------------------------------------------------------------------------------
1 | @(signInForm: Form[forms.SignUpForm.Data])(implicit request: RequestHeader, messages: Messages)
2 |
3 | @import b3.inline.fieldConstructor
4 |
5 | @main(Messages("sign.up.title")) {
6 | @request.flash.get("error").map { msg =>
7 |
8 |
×
9 |
@Messages("error") @msg
10 |
11 | }
12 |
30 | }
31 |
--------------------------------------------------------------------------------
/app/utils/ErrorHandler.scala:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import javax.inject.Inject
4 |
5 | import com.mohiva.play.silhouette.api.SecuredErrorHandler
6 | import controllers.routes
7 | import play.api.http.DefaultHttpErrorHandler
8 | import play.api.i18n.Messages
9 | import play.api.mvc.Results._
10 | import play.api.mvc.{ Result, RequestHeader }
11 | import play.api.routing.Router
12 | import play.api.{ OptionalSourceMapper, Configuration }
13 |
14 | import scala.concurrent.Future
15 |
16 | /**
17 | * A secured error handler.
18 | */
19 | class ErrorHandler @Inject() (
20 | env: play.api.Environment,
21 | config: Configuration,
22 | sourceMapper: OptionalSourceMapper,
23 | router: javax.inject.Provider[Router])
24 | extends DefaultHttpErrorHandler(env, config, sourceMapper, router)
25 | with SecuredErrorHandler {
26 |
27 | /**
28 | * Called when a user is not authenticated.
29 | *
30 | * As defined by RFC 2616, the status code of the response should be 401 Unauthorized.
31 | *
32 | * @param request The request header.
33 | * @param messages The messages for the current language.
34 | * @return The result to send to the client.
35 | */
36 | override def onNotAuthenticated(request: RequestHeader, messages: Messages): Option[Future[Result]] = {
37 | Some(Future.successful(Redirect(routes.ApplicationController.signIn())))
38 | }
39 |
40 | /**
41 | * Called when a user is authenticated but not authorized.
42 | *
43 | * As defined by RFC 2616, the status code of the response should be 403 Forbidden.
44 | *
45 | * @param request The request header.
46 | * @param messages The messages for the current language.
47 | * @return The result to send to the client.
48 | */
49 | override def onNotAuthorized(request: RequestHeader, messages: Messages): Option[Future[Result]] = {
50 | Some(Future.successful(Redirect(routes.ApplicationController.signIn()).flashing("error" -> Messages("access.denied")(messages))))
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/models/services/UserServiceImpl.scala:
--------------------------------------------------------------------------------
1 | package models.services
2 |
3 | import java.util.UUID
4 | import javax.inject.Inject
5 |
6 | import com.mohiva.play.silhouette.api.LoginInfo
7 | import com.mohiva.play.silhouette.impl.providers.CommonSocialProfile
8 | import models.User
9 | import models.daos.UserDAO
10 | import play.api.libs.concurrent.Execution.Implicits._
11 |
12 | import scala.concurrent.Future
13 |
14 | /**
15 | * Handles actions to users.
16 | *
17 | * @param userDAO The user DAO implementation.
18 | */
19 | class UserServiceImpl @Inject() (userDAO: UserDAO) extends UserService {
20 |
21 | /**
22 | * Retrieves a user that matches the specified login info.
23 | *
24 | * @param loginInfo The login info to retrieve a user.
25 | * @return The retrieved user or None if no user could be retrieved for the given login info.
26 | */
27 | def retrieve(loginInfo: LoginInfo): Future[Option[User]] = userDAO.find(loginInfo)
28 |
29 | /**
30 | * Saves a user.
31 | *
32 | * @param user The user to save.
33 | * @return The saved user.
34 | */
35 | def save(user: User) = userDAO.save(user)
36 |
37 | /**
38 | * Saves the social profile for a user.
39 | *
40 | * If a user exists for this profile then update the user, otherwise create a new user with the given profile.
41 | *
42 | * @param profile The social profile to save.
43 | * @return The user for whom the profile was saved.
44 | */
45 | def save(profile: CommonSocialProfile) = {
46 | userDAO.find(profile.loginInfo).flatMap {
47 | case Some(user) => // Update user with profile
48 | userDAO.save(user.copy(
49 | firstName = profile.firstName,
50 | lastName = profile.lastName,
51 | fullName = profile.fullName,
52 | email = profile.email,
53 | avatarURL = profile.avatarURL
54 | ))
55 | case None => // Insert a new user
56 | userDAO.save(User(
57 | userID = UUID.randomUUID(),
58 | loginInfo = profile.loginInfo,
59 | firstName = profile.firstName,
60 | lastName = profile.lastName,
61 | fullName = profile.fullName,
62 | email = profile.email,
63 | avatarURL = profile.avatarURL
64 | ))
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/controllers/ApplicationController.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import javax.inject.Inject
4 |
5 | import com.mohiva.play.silhouette.api.{ Environment, LogoutEvent, Silhouette }
6 | import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
7 | import com.mohiva.play.silhouette.impl.providers.SocialProviderRegistry
8 | import forms._
9 | import models.User
10 | import play.api.i18n.MessagesApi
11 |
12 | import scala.concurrent.Future
13 |
14 | /**
15 | * The basic application controller.
16 | *
17 | * @param messagesApi The Play messages API.
18 | * @param env The Silhouette environment.
19 | * @param socialProviderRegistry The social provider registry.
20 | */
21 | class ApplicationController @Inject() (
22 | val messagesApi: MessagesApi,
23 | val env: Environment[User, CookieAuthenticator],
24 | socialProviderRegistry: SocialProviderRegistry)
25 | extends Silhouette[User, CookieAuthenticator] {
26 |
27 | /**
28 | * Handles the index action.
29 | *
30 | * @return The result to display.
31 | */
32 | def index = SecuredAction.async { implicit request =>
33 | Future.successful(Ok(views.html.home(request.identity)))
34 | }
35 |
36 | /**
37 | * Handles the Sign In action.
38 | *
39 | * @return The result to display.
40 | */
41 | def signIn = UserAwareAction.async { implicit request =>
42 | request.identity match {
43 | case Some(user) => Future.successful(Redirect(routes.ApplicationController.index()))
44 | case None => Future.successful(Ok(views.html.signIn(SignInForm.form, socialProviderRegistry)))
45 | }
46 | }
47 |
48 | /**
49 | * Handles the Sign Up action.
50 | *
51 | * @return The result to display.
52 | */
53 | def signUp = UserAwareAction.async { implicit request =>
54 | request.identity match {
55 | case Some(user) => Future.successful(Redirect(routes.ApplicationController.index()))
56 | case None => Future.successful(Ok(views.html.signUp(SignUpForm.form)))
57 | }
58 | }
59 |
60 | /**
61 | * Handles the Sign Out action.
62 | *
63 | * @return The result to display.
64 | */
65 | def signOut = SecuredAction.async { implicit request =>
66 | val result = Redirect(routes.ApplicationController.index())
67 | env.eventBus.publish(LogoutEvent(request.identity, request, request2Messages))
68 |
69 | env.authenticatorService.discard(request.authenticator, result)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/conf/application.conf:
--------------------------------------------------------------------------------
1 | # This is the main configuration file for the application.
2 | # ~~~~~
3 |
4 | # Secret key
5 | # ~~~~~
6 | # The secret key is used to secure cryptographics functions.
7 | # If you deploy your application to several instances be sure to use the same key!
8 | play.crypto.secret="changeme"
9 |
10 | # The application languages
11 | # ~~~~~
12 | play.i18n.langs=["en"]
13 |
14 | # Registers the error handler
15 | # ~~~~~
16 | play.http.errorHandler = "utils.ErrorHandler"
17 |
18 | # Registers the request handler
19 | # ~~~~~
20 | play.http.requestHandler = "play.api.http.DefaultHttpRequestHandler"
21 |
22 | # Registers the filters
23 | # ~~~~~
24 | play.http.filters = "utils.Filters"
25 |
26 | # play-slick configuration
27 | # ~~~~~
28 | slick.dbs.default.driver="slick.driver.H2Driver$"
29 | slick.dbs.default.db.driver="org.h2.Driver"
30 | slick.dbs.default.db.url="jdbc:h2:mem:play"
31 | slick.dbs.default.db.user=sa
32 | slick.dbs.default.db.password=""
33 |
34 | # The application DI modules
35 | # ~~~~~
36 | play.modules.enabled += "modules.SilhouetteModule"
37 |
38 | # Security Filter Configuration - Content Security Policy
39 | # ~~~~~
40 | #play.filters.headers.contentSecurityPolicy
41 | # default-src
42 | # 'self'
43 | # img-src
44 | # 'self'
45 | # fbcdn-profile-a.akamaihd.net (Facebook)
46 | # *.twimg.com (Twitter)
47 | # *.googleusercontent.com (Google)
48 | # *.xingassets.com (Xing)
49 | # vk.com (VK)
50 | # *.yimg.com (Yahoo)
51 | # secure.gravatar.com
52 | # style-src
53 | # 'self'
54 | # 'unsafe-inline'
55 | # cdnjs.cloudflare.com
56 | # maxcdn.bootstrapcdn.com
57 | # cdn.jsdelivr.net
58 | # fonts.googleapis.com
59 | # 'unsafe-inline' (in-line css found in bootstrap.min.js)
60 | # font-src
61 | # 'self'
62 | # fonts.gstatic.com
63 | # fonts.googleapis.com
64 | # cdnjs.cloudflare.com
65 | # script-src
66 | # 'self'
67 | # clef.io
68 | # connect-src
69 | # 'self'
70 | # twitter.com
71 | # *.xing.com
72 | # frame-src
73 | # clef.io
74 | play.filters.headers.contentSecurityPolicy="default-src 'self'; img-src 'self' fbcdn-profile-a.akamaihd.net *.twimg.com *.googleusercontent.com *.xingassets.com vk.com *.yimg.com secure.gravatar.com; style-src 'self' 'unsafe-inline' cdnjs.cloudflare.com maxcdn.bootstrapcdn.com cdn.jsdelivr.net fonts.googleapis.com; font-src 'self' fonts.gstatic.com fonts.googleapis.com cdnjs.cloudflare.com; script-src 'self' clef.io; connect-src 'self' twitter.com *.xing.com; frame-src clef.io"
75 |
76 | include "silhouette.conf"
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Silhouette Slick Seed Template
2 | ==============================
3 |
4 | This is a fork of the official Silhouette Seed project. If you want to have a first look at Silhouette, I suggest you have a look at the [official project](https://github.com/mohiva/play-silhouette-seed).
5 |
6 | The Silhouette Seed project is an Activator template which shows how [Silhouette](https://github.com/mohiva/play-silhouette) can be implemented in a Play Framework application. It's a starting point which can be extended to fit your needs.
7 | It uses the [play-slick](https://github.com/playframework/play-slick) library for database access.
8 |
9 | ## Example
10 |
11 | [](https://heroku.com/deploy)
12 |
13 | (The "Build App" phase will take a few minutes)
14 |
15 | Currently, there is no live example of this template.
16 |
17 | ## Features
18 |
19 | * Sign Up
20 | * Sign In (Credentials)
21 | * Social Auth (Facebook, Google+, VK, Twitter, Xing, Yahoo)
22 | * Two-factor authentication with Clef
23 | * Dependency Injection with Guice
24 | * Publishing Events
25 | * Avatar service
26 | * Remember me functionality
27 | * [Security headers](https://www.playframework.com/documentation/2.4.x/SecurityHeaders)
28 | * [CSRF Protection](https://www.playframework.com/documentation/2.4.x/ScalaCsrf)
29 | * play-slick database access
30 |
31 | ## Documentation
32 |
33 | Consulate the [Silhouette documentation](http://silhouette.mohiva.com/docs) for more information. If you need help with the integration of Silhouette into your project, don't hesitate and ask questions in our [mailing list](https://groups.google.com/forum/#!forum/play-silhouette) or on [Stack Overflow](http://stackoverflow.com/questions/tagged/playframework).
34 |
35 | ### Slick
36 |
37 | The template stores all authentication information in a database via [Slick](http://slick.typesafe.com/) It uses an in memory [H2](www.h2database.com/) database by default.
38 |
39 | In order to use another database supported by Slick, you need to change the driver in your application.conf and add the corresponding JDBC driver to your dependencies. The [Play Slick documentation](https://www.playframework.com/documentation/2.4.x/PlaySlick) has more information about database configuration.
40 |
41 | ## Activator
42 |
43 | This project template is also
44 | [hosted at typesafe](https://typesafe.com/activator/template/play-silhouette-slick-seed).
45 |
46 | # License
47 |
48 | The code is licensed under [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0).
49 |
--------------------------------------------------------------------------------
/app/views/signIn.scala.html:
--------------------------------------------------------------------------------
1 | @(signInForm: Form[forms.SignInForm.Data], socialProviders: com.mohiva.play.silhouette.impl.providers.SocialProviderRegistry)(implicit request: RequestHeader, messages: Messages)
2 |
3 | @import com.mohiva.play.silhouette.impl.providers.oauth2.ClefProvider
4 | @import b3.inline.fieldConstructor
5 |
6 | @main(Messages("sign.in.title")) {
7 | @request.flash.get("error").map { msg =>
8 |
9 |
×
10 |
@Messages("error") @msg
11 |
12 | }
13 |
48 | }
49 |
--------------------------------------------------------------------------------
/app/controllers/SocialAuthController.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import javax.inject.Inject
4 |
5 | import com.mohiva.play.silhouette.api._
6 | import com.mohiva.play.silhouette.api.exceptions.ProviderException
7 | import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
8 | import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
9 | import com.mohiva.play.silhouette.impl.providers._
10 | import models.User
11 | import models.services.UserService
12 | import play.api.i18n.{ MessagesApi, Messages }
13 | import play.api.libs.concurrent.Execution.Implicits._
14 | import play.api.mvc.Action
15 |
16 | import scala.concurrent.Future
17 |
18 | /**
19 | * The social auth controller.
20 | *
21 | * @param messagesApi The Play messages API.
22 | * @param env The Silhouette environment.
23 | * @param userService The user service implementation.
24 | * @param authInfoRepository The auth info service implementation.
25 | * @param socialProviderRegistry The social provider registry.
26 | */
27 | class SocialAuthController @Inject() (
28 | val messagesApi: MessagesApi,
29 | val env: Environment[User, CookieAuthenticator],
30 | userService: UserService,
31 | authInfoRepository: AuthInfoRepository,
32 | socialProviderRegistry: SocialProviderRegistry)
33 | extends Silhouette[User, CookieAuthenticator] with Logger {
34 |
35 | /**
36 | * Authenticates a user against a social provider.
37 | *
38 | * @param provider The ID of the provider to authenticate against.
39 | * @return The result to display.
40 | */
41 | def authenticate(provider: String) = Action.async { implicit request =>
42 | (socialProviderRegistry.get[SocialProvider](provider) match {
43 | case Some(p: SocialProvider with CommonSocialProfileBuilder) =>
44 | p.authenticate().flatMap {
45 | case Left(result) => Future.successful(result)
46 | case Right(authInfo) => for {
47 | profile <- p.retrieveProfile(authInfo)
48 | user <- userService.save(profile)
49 | authInfo <- authInfoRepository.save(profile.loginInfo, authInfo)
50 | authenticator <- env.authenticatorService.create(profile.loginInfo)
51 | value <- env.authenticatorService.init(authenticator)
52 | result <- env.authenticatorService.embed(value, Redirect(routes.ApplicationController.index()))
53 | } yield {
54 | env.eventBus.publish(LoginEvent(user, request, request2Messages))
55 | result
56 | }
57 | }
58 | case _ => Future.failed(new ProviderException(s"Cannot authenticate with unexpected social provider $provider"))
59 | }).recover {
60 | case e: ProviderException =>
61 | logger.error("Unexpected provider error", e)
62 | Redirect(routes.ApplicationController.signIn()).flashing("error" -> Messages("could.not.authenticate"))
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/test/controllers/ApplicationControllerSpec.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import java.util.UUID
4 |
5 | import com.google.inject.AbstractModule
6 | import com.mohiva.play.silhouette.api.{ Environment, LoginInfo }
7 | import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
8 | import com.mohiva.play.silhouette.test._
9 | import models.User
10 | import net.codingwell.scalaguice.ScalaModule
11 | import org.specs2.mock.Mockito
12 | import org.specs2.specification.Scope
13 | import play.api.inject.guice.GuiceApplicationBuilder
14 | import play.api.libs.concurrent.Execution.Implicits._
15 | import play.api.test.{ FakeRequest, PlaySpecification, WithApplication }
16 |
17 | /**
18 | * Test case for the [[controllers.ApplicationController]] class.
19 | */
20 | class ApplicationControllerSpec extends PlaySpecification with Mockito {
21 | sequential
22 |
23 | "The `index` action" should {
24 | "redirect to login page if user is unauthorized" in new Context {
25 | new WithApplication(application) {
26 | val Some(redirectResult) = route(FakeRequest(routes.ApplicationController.index())
27 | .withAuthenticator[CookieAuthenticator](LoginInfo("invalid", "invalid"))
28 | )
29 |
30 | status(redirectResult) must be equalTo SEE_OTHER
31 |
32 | val redirectURL = redirectLocation(redirectResult).getOrElse("")
33 | redirectURL must contain(routes.ApplicationController.signIn().toString())
34 |
35 | val Some(unauthorizedResult) = route(FakeRequest(GET, redirectURL))
36 |
37 | status(unauthorizedResult) must be equalTo OK
38 | contentType(unauthorizedResult) must beSome("text/html")
39 | contentAsString(unauthorizedResult) must contain("Silhouette - Sign In")
40 | }
41 | }
42 |
43 | "return 200 if user is authorized" in new Context {
44 | new WithApplication(application) {
45 | val Some(result) = route(FakeRequest(routes.ApplicationController.index())
46 | .withAuthenticator[CookieAuthenticator](identity.loginInfo)
47 | )
48 |
49 | status(result) must beEqualTo(OK)
50 | }
51 | }
52 | }
53 |
54 | /**
55 | * The context.
56 | */
57 | trait Context extends Scope {
58 |
59 | /**
60 | * A fake Guice module.
61 | */
62 | class FakeModule extends AbstractModule with ScalaModule {
63 | def configure() = {
64 | bind[Environment[User, CookieAuthenticator]].toInstance(env)
65 | }
66 | }
67 |
68 | /**
69 | * An identity.
70 | */
71 | val identity = User(
72 | userID = UUID.randomUUID(),
73 | loginInfo = LoginInfo("facebook", "user@facebook.com"),
74 | firstName = None,
75 | lastName = None,
76 | fullName = None,
77 | email = None,
78 | avatarURL = None
79 | )
80 |
81 | /**
82 | * A Silhouette fake environment.
83 | */
84 | implicit val env: Environment[User, CookieAuthenticator] = new FakeEnvironment[User, CookieAuthenticator](Seq(identity.loginInfo -> identity))
85 |
86 | /**
87 | * The application.
88 | */
89 | lazy val application = new GuiceApplicationBuilder()
90 | .overrides(new FakeModule)
91 | .build()
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/controllers/SignUpController.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import java.util.UUID
4 | import javax.inject.Inject
5 |
6 | import com.mohiva.play.silhouette.api._
7 | import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
8 | import com.mohiva.play.silhouette.api.services.AvatarService
9 | import com.mohiva.play.silhouette.api.util.PasswordHasher
10 | import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
11 | import com.mohiva.play.silhouette.impl.providers._
12 | import forms.SignUpForm
13 | import models.User
14 | import models.services.UserService
15 | import play.api.i18n.{ MessagesApi, Messages }
16 | import play.api.libs.concurrent.Execution.Implicits._
17 | import play.api.mvc.Action
18 |
19 | import scala.concurrent.Future
20 |
21 | /**
22 | * The sign up controller.
23 | *
24 | * @param messagesApi The Play messages API.
25 | * @param env The Silhouette environment.
26 | * @param userService The user service implementation.
27 | * @param authInfoRepository The auth info repository implementation.
28 | * @param avatarService The avatar service implementation.
29 | * @param passwordHasher The password hasher implementation.
30 | */
31 | class SignUpController @Inject() (
32 | val messagesApi: MessagesApi,
33 | val env: Environment[User, CookieAuthenticator],
34 | userService: UserService,
35 | authInfoRepository: AuthInfoRepository,
36 | avatarService: AvatarService,
37 | passwordHasher: PasswordHasher)
38 | extends Silhouette[User, CookieAuthenticator] {
39 |
40 | /**
41 | * Registers a new user.
42 | *
43 | * @return The result to display.
44 | */
45 | def signUp = Action.async { implicit request =>
46 | SignUpForm.form.bindFromRequest.fold(
47 | form => Future.successful(BadRequest(views.html.signUp(form))),
48 | data => {
49 | val loginInfo = LoginInfo(CredentialsProvider.ID, data.email)
50 | userService.retrieve(loginInfo).flatMap {
51 | case Some(user) =>
52 | Future.successful(Redirect(routes.ApplicationController.signUp()).flashing("error" -> Messages("user.exists")))
53 | case None =>
54 | val authInfo = passwordHasher.hash(data.password)
55 | val user = User(
56 | userID = UUID.randomUUID(),
57 | loginInfo = loginInfo,
58 | firstName = Some(data.firstName),
59 | lastName = Some(data.lastName),
60 | fullName = Some(data.firstName + " " + data.lastName),
61 | email = Some(data.email),
62 | avatarURL = None
63 | )
64 | for {
65 | avatar <- avatarService.retrieveURL(data.email)
66 | user <- userService.save(user.copy(avatarURL = avatar))
67 | authInfo <- authInfoRepository.add(loginInfo, authInfo)
68 | authenticator <- env.authenticatorService.create(loginInfo)
69 | value <- env.authenticatorService.init(authenticator)
70 | result <- env.authenticatorService.embed(value, Redirect(routes.ApplicationController.index()))
71 | } yield {
72 | env.eventBus.publish(SignUpEvent(user, request, request2Messages))
73 | env.eventBus.publish(LoginEvent(user, request, request2Messages))
74 | result
75 | }
76 | }
77 | }
78 | )
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/app/views/main.scala.html:
--------------------------------------------------------------------------------
1 | @(title: String, user: Option[models.User] = None)(content: Html)(implicit messages: Messages)
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | @title
11 |
12 |
13 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
34 |
49 |
50 |
51 |
52 |
53 | @content
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/models/daos/UserDAOImpl.scala:
--------------------------------------------------------------------------------
1 | package models.daos
2 |
3 | import java.util.UUID
4 | import com.mohiva.play.silhouette.api.LoginInfo
5 | import models.User
6 | import play.api.libs.concurrent.Execution.Implicits.defaultContext
7 | import slick.dbio.DBIOAction
8 | import javax.inject.Inject
9 | import play.api.db.slick.DatabaseConfigProvider
10 | import scala.concurrent.Future
11 |
12 | /**
13 | * Give access to the user object using Slick
14 | */
15 | class UserDAOImpl @Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends UserDAO with DAOSlick {
16 |
17 | import driver.api._
18 |
19 | /**
20 | * Finds a user by its login info.
21 | *
22 | * @param loginInfo The login info of the user to find.
23 | * @return The found user or None if no user for the given login info could be found.
24 | */
25 | def find(loginInfo: LoginInfo) = {
26 | val userQuery = for {
27 | dbLoginInfo <- loginInfoQuery(loginInfo)
28 | dbUserLoginInfo <- slickUserLoginInfos.filter(_.loginInfoId === dbLoginInfo.id)
29 | dbUser <- slickUsers.filter(_.id === dbUserLoginInfo.userID)
30 | } yield dbUser
31 | db.run(userQuery.result.headOption).map { dbUserOption =>
32 | dbUserOption.map { user =>
33 | User(UUID.fromString(user.userID), loginInfo, user.firstName, user.lastName, user.fullName, user.email, user.avatarURL)
34 | }
35 | }
36 | }
37 |
38 | /**
39 | * Finds a user by its user ID.
40 | *
41 | * @param userID The ID of the user to find.
42 | * @return The found user or None if no user for the given ID could be found.
43 | */
44 | def find(userID: UUID) = {
45 | val query = for {
46 | dbUser <- slickUsers.filter(_.id === userID.toString)
47 | dbUserLoginInfo <- slickUserLoginInfos.filter(_.userID === dbUser.id)
48 | dbLoginInfo <- slickLoginInfos.filter(_.id === dbUserLoginInfo.loginInfoId)
49 | } yield (dbUser, dbLoginInfo)
50 | db.run(query.result.headOption).map { resultOption =>
51 | resultOption.map {
52 | case (user, loginInfo) =>
53 | User(
54 | UUID.fromString(user.userID),
55 | LoginInfo(loginInfo.providerID, loginInfo.providerKey),
56 | user.firstName,
57 | user.lastName,
58 | user.fullName,
59 | user.email,
60 | user.avatarURL)
61 | }
62 | }
63 | }
64 |
65 | /**
66 | * Saves a user.
67 | *
68 | * @param user The user to save.
69 | * @return The saved user.
70 | */
71 | def save(user: User) = {
72 | val dbUser = DBUser(user.userID.toString, user.firstName, user.lastName, user.fullName, user.email, user.avatarURL)
73 | val dbLoginInfo = DBLoginInfo(None, user.loginInfo.providerID, user.loginInfo.providerKey)
74 | // We don't have the LoginInfo id so we try to get it first.
75 | // If there is no LoginInfo yet for this user we retrieve the id on insertion.
76 | val loginInfoAction = {
77 | val retrieveLoginInfo = slickLoginInfos.filter(
78 | info => info.providerID === user.loginInfo.providerID &&
79 | info.providerKey === user.loginInfo.providerKey).result.headOption
80 | val insertLoginInfo = slickLoginInfos.returning(slickLoginInfos.map(_.id)).
81 | into((info, id) => info.copy(id = Some(id))) += dbLoginInfo
82 | for {
83 | loginInfoOption <- retrieveLoginInfo
84 | loginInfo <- loginInfoOption.map(DBIO.successful(_)).getOrElse(insertLoginInfo)
85 | } yield loginInfo
86 | }
87 | // combine database actions to be run sequentially
88 | val actions = (for {
89 | _ <- slickUsers.insertOrUpdate(dbUser)
90 | loginInfo <- loginInfoAction
91 | _ <- slickUserLoginInfos += DBUserLoginInfo(dbUser.userID, loginInfo.id.get)
92 | } yield ()).transactionally
93 | // run actions and return user afterwards
94 | db.run(actions).map(_ => user)
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/controllers/CredentialsAuthController.scala:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import javax.inject.Inject
4 |
5 | import com.mohiva.play.silhouette.api.Authenticator.Implicits._
6 | import com.mohiva.play.silhouette.api._
7 | import com.mohiva.play.silhouette.api.exceptions.ProviderException
8 | import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
9 | import com.mohiva.play.silhouette.api.util.{ Clock, Credentials }
10 | import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
11 | import com.mohiva.play.silhouette.impl.exceptions.IdentityNotFoundException
12 | import com.mohiva.play.silhouette.impl.providers._
13 | import forms.SignInForm
14 | import models.User
15 | import models.services.UserService
16 | import net.ceedubs.ficus.Ficus._
17 | import play.api.Configuration
18 | import play.api.i18n.{ Messages, MessagesApi }
19 | import play.api.libs.concurrent.Execution.Implicits._
20 | import play.api.mvc.Action
21 |
22 | import scala.concurrent.Future
23 | import scala.concurrent.duration._
24 | import scala.language.postfixOps
25 |
26 | /**
27 | * The credentials auth controller.
28 | *
29 | * @param messagesApi The Play messages API.
30 | * @param env The Silhouette environment.
31 | * @param userService The user service implementation.
32 | * @param authInfoRepository The auth info repository implementation.
33 | * @param credentialsProvider The credentials provider.
34 | * @param socialProviderRegistry The social provider registry.
35 | * @param configuration The Play configuration.
36 | * @param clock The clock instance.
37 | */
38 | class CredentialsAuthController @Inject() (
39 | val messagesApi: MessagesApi,
40 | val env: Environment[User, CookieAuthenticator],
41 | userService: UserService,
42 | authInfoRepository: AuthInfoRepository,
43 | credentialsProvider: CredentialsProvider,
44 | socialProviderRegistry: SocialProviderRegistry,
45 | configuration: Configuration,
46 | clock: Clock)
47 | extends Silhouette[User, CookieAuthenticator] {
48 |
49 | /**
50 | * Authenticates a user against the credentials provider.
51 | *
52 | * @return The result to display.
53 | */
54 | def authenticate = Action.async { implicit request =>
55 | SignInForm.form.bindFromRequest.fold(
56 | form => Future.successful(BadRequest(views.html.signIn(form, socialProviderRegistry))),
57 | data => {
58 | val credentials = Credentials(data.email, data.password)
59 | credentialsProvider.authenticate(credentials).flatMap { loginInfo =>
60 | val result = Redirect(routes.ApplicationController.index())
61 | userService.retrieve(loginInfo).flatMap {
62 | case Some(user) =>
63 | val c = configuration.underlying
64 | env.authenticatorService.create(loginInfo).map {
65 | case authenticator if data.rememberMe =>
66 | authenticator.copy(
67 | expirationDateTime = clock.now + c.as[FiniteDuration]("silhouette.authenticator.rememberMe.authenticatorExpiry"),
68 | idleTimeout = c.getAs[FiniteDuration]("silhouette.authenticator.rememberMe.authenticatorIdleTimeout"),
69 | cookieMaxAge = c.getAs[FiniteDuration]("silhouette.authenticator.rememberMe.cookieMaxAge")
70 | )
71 | case authenticator => authenticator
72 | }.flatMap { authenticator =>
73 | env.eventBus.publish(LoginEvent(user, request, request2Messages))
74 | env.authenticatorService.init(authenticator).flatMap { v =>
75 | env.authenticatorService.embed(v, result)
76 | }
77 | }
78 | case None => Future.failed(new IdentityNotFoundException("Couldn't find user"))
79 | }
80 | }.recover {
81 | case e: ProviderException =>
82 | Redirect(routes.ApplicationController.signIn()).flashing("error" -> Messages("invalid.credentials"))
83 | }
84 | }
85 | )
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/conf/silhouette.conf:
--------------------------------------------------------------------------------
1 | silhouette {
2 |
3 | # Authenticator settings
4 | authenticator.cookieName="authenticator"
5 | authenticator.cookiePath="/"
6 | authenticator.secureCookie=false // Disabled for testing on localhost without SSL, otherwise cookie couldn't be set
7 | authenticator.httpOnlyCookie=true
8 | authenticator.useFingerprinting=true
9 | authenticator.authenticatorIdleTimeout=30 minutes
10 | authenticator.authenticatorExpiry=12 hours
11 |
12 | authenticator.rememberMe.cookieMaxAge=30 days
13 | authenticator.rememberMe.authenticatorIdleTimeout=5 days
14 | authenticator.rememberMe.authenticatorExpiry=30 days
15 |
16 | # OAuth1 token secret provider settings
17 | oauth1TokenSecretProvider.cookieName="OAuth1TokenSecret"
18 | oauth1TokenSecretProvider.cookiePath="/"
19 | oauth1TokenSecretProvider.secureCookie=false // Disabled for testing on localhost without SSL, otherwise cookie couldn't be set
20 | oauth1TokenSecretProvider.httpOnlyCookie=true
21 | oauth1TokenSecretProvider.expirationTime=5 minutes
22 |
23 | # OAuth2 state provider settings
24 | oauth2StateProvider.cookieName="OAuth2State"
25 | oauth2StateProvider.cookiePath="/"
26 | oauth2StateProvider.secureCookie=false // Disabled for testing on localhost without SSL, otherwise cookie couldn't be set
27 | oauth2StateProvider.httpOnlyCookie=true
28 | oauth2StateProvider.expirationTime=5 minutes
29 |
30 | # Facebook provider
31 | facebook.authorizationURL="https://graph.facebook.com/v2.3/oauth/authorize"
32 | facebook.accessTokenURL="https://graph.facebook.com/v2.3/oauth/access_token"
33 | facebook.redirectURL="http://localhost:9000/authenticate/facebook"
34 | facebook.clientID=""
35 | facebook.clientID=${?FACEBOOK_CLIENT_ID}
36 | facebook.clientSecret=""
37 | facebook.clientSecret=${?FACEBOOK_CLIENT_SECRET}
38 | facebook.scope="email"
39 |
40 | # Google provider
41 | google.authorizationURL="https://accounts.google.com/o/oauth2/auth"
42 | google.accessTokenURL="https://accounts.google.com/o/oauth2/token"
43 | google.redirectURL="http://localhost:9000/authenticate/google"
44 | google.clientID=""
45 | google.clientID=${?GOOGLE_CLIENT_ID}
46 | google.clientSecret=""
47 | google.clientSecret=${?GOOGLE_CLIENT_SECRET}
48 | google.scope="profile email"
49 |
50 | # VK provider
51 | vk.authorizationURL="http://oauth.vk.com/authorize"
52 | vk.accessTokenURL="https://oauth.vk.com/access_token"
53 | vk.redirectURL="http://localhost:9000/authenticate/vk"
54 | vk.clientID=""
55 | vk.clientID=${?VK_CLIENT_ID}
56 | vk.clientSecret=""
57 | vk.clientSecret=${?VK_CLIENT_SECRET}
58 | vk.scope="email"
59 |
60 | # Clef provider
61 | clef.accessTokenURL="https://clef.io/api/v1/authorize"
62 | clef.redirectURL="http://localhost:9000/authenticate/clef"
63 | clef.clientID=""
64 | clef.clientID=${?CLEF_CLIENT_ID}
65 | clef.clientSecret=""
66 | clef.clientSecret=${?CLEF_CLIENT_SECRET}
67 |
68 | # Twitter provider
69 | twitter.requestTokenURL="https://twitter.com/oauth/request_token"
70 | twitter.accessTokenURL="https://twitter.com/oauth/access_token"
71 | twitter.authorizationURL="https://twitter.com/oauth/authenticate"
72 | twitter.callbackURL="http://localhost:9000/authenticate/twitter"
73 | twitter.consumerKey=""
74 | twitter.consumerKey=${?TWITTER_CONSUMER_KEY}
75 | twitter.consumerSecret=""
76 | twitter.consumerSecret=${?TWITTER_CONSUMER_SECRET}
77 |
78 | # Xing provider
79 | xing.requestTokenURL="https://api.xing.com/v1/request_token"
80 | xing.accessTokenURL="https://api.xing.com/v1/access_token"
81 | xing.authorizationURL="https://api.xing.com/v1/authorize"
82 | xing.callbackURL="http://localhost:9000/authenticate/xing"
83 | xing.consumerKey=""
84 | xing.consumerKey=${?XING_CONSUMER_KEY}
85 | xing.consumerSecret=""
86 | xing.consumerSecret=${?XING_CONSUMER_SECRET}
87 |
88 | # Yahoo provider
89 | yahoo.providerURL="https://me.yahoo.com/"
90 | yahoo.callbackURL="http://localhost:9000/authenticate/yahoo"
91 | yahoo.axRequired={
92 | "fullname": "http://axschema.org/namePerson",
93 | "email": "http://axschema.org/contact/email",
94 | "image": "http://axschema.org/media/image/default"
95 | }
96 | yahoo.realm="http://localhost:9000"
97 | }
98 |
--------------------------------------------------------------------------------
/app/models/daos/OAuth1InfoDAO.scala:
--------------------------------------------------------------------------------
1 | package models.daos
2 |
3 | import com.mohiva.play.silhouette.api.LoginInfo
4 | import com.mohiva.play.silhouette.impl.daos.DelegableAuthInfoDAO
5 | import com.mohiva.play.silhouette.impl.providers.OAuth1Info
6 | import javax.inject.Inject
7 | import play.api.libs.concurrent.Execution.Implicits._
8 | import play.api.db.slick.DatabaseConfigProvider
9 | import scala.concurrent.Future
10 |
11 | /**
12 | * The DAO to store the OAuth1 information.
13 | */
14 | class OAuth1InfoDAO @Inject() (protected val dbConfigProvider: DatabaseConfigProvider)
15 | extends DelegableAuthInfoDAO[OAuth1Info] with DAOSlick {
16 |
17 | import driver.api._
18 |
19 | protected def oAuth1InfoQuery(loginInfo: LoginInfo) = for {
20 | dbLoginInfo <- loginInfoQuery(loginInfo)
21 | dbOAuth1Info <- slickOAuth1Infos if dbOAuth1Info.loginInfoId === dbLoginInfo.id
22 | } yield dbOAuth1Info
23 |
24 | // Use subquery workaround instead of join to get authinfo because slick only supports selecting
25 | // from a single table for update/delete queries (https://github.com/slick/slick/issues/684).
26 | protected def oAuth1InfoSubQuery(loginInfo: LoginInfo) =
27 | slickOAuth1Infos.filter(_.loginInfoId in loginInfoQuery(loginInfo).map(_.id))
28 |
29 | protected def addAction(loginInfo: LoginInfo, authInfo: OAuth1Info) =
30 | loginInfoQuery(loginInfo).result.head.flatMap { dbLoginInfo =>
31 | slickOAuth1Infos += DBOAuth1Info(None, authInfo.token, authInfo.secret, dbLoginInfo.id.get)
32 | }.transactionally
33 |
34 | protected def updateAction(loginInfo: LoginInfo, authInfo: OAuth1Info) =
35 | oAuth1InfoSubQuery(loginInfo).
36 | map(dbOAuthInfo => (dbOAuthInfo.token, dbOAuthInfo.secret)).
37 | update((authInfo.token, authInfo.secret))
38 |
39 | /**
40 | * Finds the auth info which is linked with the specified login info.
41 | *
42 | * @param loginInfo The linked login info.
43 | * @return The retrieved auth info or None if no auth info could be retrieved for the given login info.
44 | */
45 | def find(loginInfo: LoginInfo): Future[Option[OAuth1Info]] = {
46 | val result = db.run(oAuth1InfoQuery(loginInfo).result.headOption)
47 | result.map { dbOAuth1InfoOption =>
48 | dbOAuth1InfoOption.map(dbOAuth1Info => OAuth1Info(dbOAuth1Info.token, dbOAuth1Info.secret))
49 | }
50 | }
51 |
52 | /**
53 | * Adds new auth info for the given login info.
54 | *
55 | * @param loginInfo The login info for which the auth info should be added.
56 | * @param authInfo The auth info to add.
57 | * @return The added auth info.
58 | */
59 | def add(loginInfo: LoginInfo, authInfo: OAuth1Info): Future[OAuth1Info] =
60 | db.run(addAction(loginInfo, authInfo)).map(_ => authInfo)
61 |
62 | /**
63 | * Updates the auth info for the given login info.
64 | *
65 | * @param loginInfo The login info for which the auth info should be updated.
66 | * @param authInfo The auth info to update.
67 | * @return The updated auth info.
68 | */
69 | def update(loginInfo: LoginInfo, authInfo: OAuth1Info): Future[OAuth1Info] =
70 | db.run(updateAction(loginInfo, authInfo)).map(_ => authInfo)
71 |
72 | /**
73 | * Saves the auth info for the given login info.
74 | *
75 | * This method either adds the auth info if it doesn't exists or it updates the auth info
76 | * if it already exists.
77 | *
78 | * @param loginInfo The login info for which the auth info should be saved.
79 | * @param authInfo The auth info to save.
80 | * @return The saved auth info.
81 | */
82 | def save(loginInfo: LoginInfo, authInfo: OAuth1Info): Future[OAuth1Info] = {
83 | val query = loginInfoQuery(loginInfo).joinLeft(slickOAuth1Infos).on(_.id === _.loginInfoId)
84 | val action = query.result.head.flatMap {
85 | case (dbLoginInfo, Some(dbOAuth1Info)) => updateAction(loginInfo, authInfo)
86 | case (dbLoginInfo, None) => addAction(loginInfo, authInfo)
87 | }.transactionally
88 | db.run(action).map(_ => authInfo)
89 | }
90 |
91 | /**
92 | * Removes the auth info for the given login info.
93 | *
94 | * @param loginInfo The login info for which the auth info should be removed.
95 | * @return A future to wait for the process to be completed.
96 | */
97 | def remove(loginInfo: LoginInfo): Future[Unit] =
98 | db.run(oAuth1InfoSubQuery(loginInfo).delete).map(_ => ())
99 | }
100 |
--------------------------------------------------------------------------------
/app/models/daos/PasswordInfoDAO.scala:
--------------------------------------------------------------------------------
1 | package models.daos
2 |
3 | import com.mohiva.play.silhouette.api.LoginInfo
4 | import com.mohiva.play.silhouette.api.util.PasswordInfo
5 | import com.mohiva.play.silhouette.impl.daos.DelegableAuthInfoDAO
6 | import play.api.libs.concurrent.Execution.Implicits._
7 | import javax.inject.Inject
8 | import play.api.libs.concurrent.Execution.Implicits._
9 | import play.api.db.slick.DatabaseConfigProvider
10 | import scala.concurrent.Future
11 |
12 | /**
13 | * The DAO to store the password information.
14 | */
15 | class PasswordInfoDAO @Inject() (protected val dbConfigProvider: DatabaseConfigProvider)
16 | extends DelegableAuthInfoDAO[PasswordInfo] with DAOSlick {
17 |
18 | import driver.api._
19 |
20 | protected def passwordInfoQuery(loginInfo: LoginInfo) = for {
21 | dbLoginInfo <- loginInfoQuery(loginInfo)
22 | dbPasswordInfo <- slickPasswordInfos if dbPasswordInfo.loginInfoId === dbLoginInfo.id
23 | } yield dbPasswordInfo
24 |
25 | // Use subquery workaround instead of join to get authinfo because slick only supports selecting
26 | // from a single table for update/delete queries (https://github.com/slick/slick/issues/684).
27 | protected def passwordInfoSubQuery(loginInfo: LoginInfo) =
28 | slickPasswordInfos.filter(_.loginInfoId in loginInfoQuery(loginInfo).map(_.id))
29 |
30 | protected def addAction(loginInfo: LoginInfo, authInfo: PasswordInfo) =
31 | loginInfoQuery(loginInfo).result.head.flatMap { dbLoginInfo =>
32 | slickPasswordInfos +=
33 | DBPasswordInfo(authInfo.hasher, authInfo.password, authInfo.salt, dbLoginInfo.id.get)
34 | }.transactionally
35 |
36 | protected def updateAction(loginInfo: LoginInfo, authInfo: PasswordInfo) =
37 | passwordInfoSubQuery(loginInfo).
38 | map(dbPasswordInfo => (dbPasswordInfo.hasher, dbPasswordInfo.password, dbPasswordInfo.salt)).
39 | update((authInfo.hasher, authInfo.password, authInfo.salt))
40 |
41 | /**
42 | * Finds the auth info which is linked with the specified login info.
43 | *
44 | * @param loginInfo The linked login info.
45 | * @return The retrieved auth info or None if no auth info could be retrieved for the given login info.
46 | */
47 | def find(loginInfo: LoginInfo): Future[Option[PasswordInfo]] = {
48 | db.run(passwordInfoQuery(loginInfo).result.headOption).map { dbPasswordInfoOption =>
49 | dbPasswordInfoOption.map(dbPasswordInfo =>
50 | PasswordInfo(dbPasswordInfo.hasher, dbPasswordInfo.password, dbPasswordInfo.salt))
51 | }
52 | }
53 |
54 | /**
55 | * Adds new auth info for the given login info.
56 | *
57 | * @param loginInfo The login info for which the auth info should be added.
58 | * @param authInfo The auth info to add.
59 | * @return The added auth info.
60 | */
61 | def add(loginInfo: LoginInfo, authInfo: PasswordInfo): Future[PasswordInfo] =
62 | db.run(addAction(loginInfo, authInfo)).map(_ => authInfo)
63 |
64 | /**
65 | * Updates the auth info for the given login info.
66 | *
67 | * @param loginInfo The login info for which the auth info should be updated.
68 | * @param authInfo The auth info to update.
69 | * @return The updated auth info.
70 | */
71 | def update(loginInfo: LoginInfo, authInfo: PasswordInfo): Future[PasswordInfo] =
72 | db.run(updateAction(loginInfo, authInfo)).map(_ => authInfo)
73 |
74 | /**
75 | * Saves the auth info for the given login info.
76 | *
77 | * This method either adds the auth info if it doesn't exists or it updates the auth info
78 | * if it already exists.
79 | *
80 | * @param loginInfo The login info for which the auth info should be saved.
81 | * @param authInfo The auth info to save.
82 | * @return The saved auth info.
83 | */
84 | def save(loginInfo: LoginInfo, authInfo: PasswordInfo): Future[PasswordInfo] = {
85 | val query = loginInfoQuery(loginInfo).joinLeft(slickPasswordInfos).on(_.id === _.loginInfoId)
86 | val action = query.result.head.flatMap {
87 | case (dbLoginInfo, Some(dbPasswordInfo)) => updateAction(loginInfo, authInfo)
88 | case (dbLoginInfo, None) => addAction(loginInfo, authInfo)
89 | }
90 | db.run(action).map(_ => authInfo)
91 | }
92 |
93 | /**
94 | * Removes the auth info for the given login info.
95 | *
96 | * @param loginInfo The login info for which the auth info should be removed.
97 | * @return A future to wait for the process to be completed.
98 | */
99 | def remove(loginInfo: LoginInfo): Future[Unit] =
100 | db.run(passwordInfoSubQuery(loginInfo).delete).map(_ => ())
101 | }
102 |
--------------------------------------------------------------------------------
/app/models/daos/OAuth2InfoDAO.scala:
--------------------------------------------------------------------------------
1 | package models.daos
2 |
3 | import com.mohiva.play.silhouette.api.LoginInfo
4 | import com.mohiva.play.silhouette.impl.daos.DelegableAuthInfoDAO
5 | import com.mohiva.play.silhouette.impl.providers.OAuth2Info
6 | import javax.inject.Inject
7 | import play.api.libs.concurrent.Execution.Implicits._
8 | import play.api.db.slick.DatabaseConfigProvider
9 | import scala.concurrent.Future
10 |
11 | /**
12 | * The DAO to store the OAuth2 information.
13 | */
14 | class OAuth2InfoDAO @Inject() (protected val dbConfigProvider: DatabaseConfigProvider)
15 | extends DelegableAuthInfoDAO[OAuth2Info] with DAOSlick {
16 |
17 | import driver.api._
18 |
19 | protected def oAuth2InfoQuery(loginInfo: LoginInfo) = for {
20 | dbLoginInfo <- loginInfoQuery(loginInfo)
21 | dbOAuth2Info <- slickOAuth2Infos if dbOAuth2Info.loginInfoId === dbLoginInfo.id
22 | } yield dbOAuth2Info
23 |
24 | // Use subquery workaround instead of join to get authinfo because slick only supports selecting
25 | // from a single table for update/delete queries (https://github.com/slick/slick/issues/684).
26 | protected def oAuth2InfoSubQuery(loginInfo: LoginInfo) =
27 | slickOAuth2Infos.filter(_.loginInfoId in loginInfoQuery(loginInfo).map(_.id))
28 |
29 | protected def addAction(loginInfo: LoginInfo, authInfo: OAuth2Info) =
30 | loginInfoQuery(loginInfo).result.head.flatMap { dbLoginInfo =>
31 | slickOAuth2Infos += DBOAuth2Info(
32 | None,
33 | authInfo.accessToken,
34 | authInfo.tokenType,
35 | authInfo.expiresIn,
36 | authInfo.refreshToken,
37 | dbLoginInfo.id.get)
38 | }.transactionally
39 |
40 | def updateAction(loginInfo: LoginInfo, authInfo: OAuth2Info) =
41 | oAuth2InfoSubQuery(loginInfo).
42 | map(dbOAuth2Info => (dbOAuth2Info.accessToken, dbOAuth2Info.tokenType, dbOAuth2Info.expiresIn, dbOAuth2Info.refreshToken)).
43 | update((authInfo.accessToken, authInfo.tokenType, authInfo.expiresIn, authInfo.refreshToken))
44 |
45 | /**
46 | * Finds the auth info which is linked with the specified login info.
47 | *
48 | * @param loginInfo The linked login info.
49 | * @return The retrieved auth info or None if no auth info could be retrieved for the given login info.
50 | */
51 | def find(loginInfo: LoginInfo): Future[Option[OAuth2Info]] = {
52 | val result = db.run(oAuth2InfoQuery(loginInfo).result.headOption)
53 | result.map { dbOAuth2InfoOption =>
54 | dbOAuth2InfoOption.map { dbOAuth2Info =>
55 | OAuth2Info(dbOAuth2Info.accessToken, dbOAuth2Info.tokenType, dbOAuth2Info.expiresIn, dbOAuth2Info.refreshToken)
56 | }
57 | }
58 | }
59 |
60 | /**
61 | * Adds new auth info for the given login info.
62 | *
63 | * @param loginInfo The login info for which the auth info should be added.
64 | * @param authInfo The auth info to add.
65 | * @return The added auth info.
66 | */
67 | def add(loginInfo: LoginInfo, authInfo: OAuth2Info): Future[OAuth2Info] =
68 | db.run(addAction(loginInfo, authInfo)).map(_ => authInfo)
69 |
70 | /**
71 | * Updates the auth info for the given login info.
72 | *
73 | * @param loginInfo The login info for which the auth info should be updated.
74 | * @param authInfo The auth info to update.
75 | * @return The updated auth info.
76 | */
77 | def update(loginInfo: LoginInfo, authInfo: OAuth2Info): Future[OAuth2Info] =
78 | db.run(updateAction(loginInfo, authInfo)).map(_ => authInfo)
79 |
80 | /**
81 | * Saves the auth info for the given login info.
82 | *
83 | * This method either adds the auth info if it doesn't exists or it updates the auth info
84 | * if it already exists.
85 | *
86 | * @param loginInfo The login info for which the auth info should be saved.
87 | * @param authInfo The auth info to save.
88 | * @return The saved auth info.
89 | */
90 | def save(loginInfo: LoginInfo, authInfo: OAuth2Info): Future[OAuth2Info] = {
91 | val query = for {
92 | result <- loginInfoQuery(loginInfo).joinLeft(slickOAuth2Infos).on(_.id === _.loginInfoId)
93 | } yield result
94 | val action = query.result.head.flatMap {
95 | case (dbLoginInfo, Some(dbOAuth2Info)) => updateAction(loginInfo, authInfo)
96 | case (dbLoginInfo, None) => addAction(loginInfo, authInfo)
97 | }.transactionally
98 | db.run(action).map(_ => authInfo)
99 | }
100 |
101 | /**
102 | * Removes the auth info for the given login info.
103 | *
104 | * @param loginInfo The login info for which the auth info should be removed.
105 | * @return A future to wait for the process to be completed.
106 | */
107 | def remove(loginInfo: LoginInfo): Future[Unit] =
108 | db.run(oAuth2InfoSubQuery(loginInfo).delete).map(_ => ())
109 | }
110 |
--------------------------------------------------------------------------------
/app/models/daos/OpenIDInfoDAO.scala:
--------------------------------------------------------------------------------
1 | package models.daos
2 |
3 | import com.mohiva.play.silhouette.api.LoginInfo
4 | import com.mohiva.play.silhouette.impl.daos.DelegableAuthInfoDAO
5 | import com.mohiva.play.silhouette.impl.providers.OpenIDInfo
6 | import javax.inject.Inject
7 | import play.api.libs.concurrent.Execution.Implicits._
8 | import play.api.db.slick.DatabaseConfigProvider
9 | import scala.concurrent.Future
10 |
11 | /**
12 | * The DAO to store the OpenID information.
13 | */
14 | class OpenIDInfoDAO @Inject() (protected val dbConfigProvider: DatabaseConfigProvider)
15 | extends DelegableAuthInfoDAO[OpenIDInfo] with DAOSlick {
16 |
17 | import driver.api._
18 |
19 | protected def openIDInfoQuery(loginInfo: LoginInfo) = for {
20 | dbLoginInfo <- loginInfoQuery(loginInfo)
21 | dbOpenIDInfo <- slickOpenIDInfos if dbOpenIDInfo.loginInfoId === dbLoginInfo.id
22 | } yield dbOpenIDInfo
23 |
24 | protected def addAction(loginInfo: LoginInfo, authInfo: OpenIDInfo) =
25 | loginInfoQuery(loginInfo).result.head.flatMap { dbLoginInfo =>
26 | DBIO.seq(
27 | slickOpenIDInfos += DBOpenIDInfo(authInfo.id, dbLoginInfo.id.get),
28 | slickOpenIDAttributes ++= authInfo.attributes.map {
29 | case (key, value) => DBOpenIDAttribute(authInfo.id, key, value)
30 | })
31 | }.transactionally
32 |
33 | protected def updateAction(loginInfo: LoginInfo, authInfo: OpenIDInfo) =
34 | openIDInfoQuery(loginInfo).result.head.flatMap { dbOpenIDInfo =>
35 | DBIO.seq(
36 | slickOpenIDInfos filter(_.id === dbOpenIDInfo.id) update dbOpenIDInfo.copy(id = authInfo.id),
37 | slickOpenIDAttributes.filter(_.id === dbOpenIDInfo.id).delete,
38 | slickOpenIDAttributes ++= authInfo.attributes.map {
39 | case (key, value) => DBOpenIDAttribute(authInfo.id, key, value)
40 | })
41 | }.transactionally
42 |
43 | /**
44 | * Finds the auth info which is linked with the specified login info.
45 | *
46 | * @param loginInfo The linked login info.
47 | * @return The retrieved auth info or None if no auth info could be retrieved for the given login info.
48 | */
49 | def find(loginInfo: LoginInfo): Future[Option[OpenIDInfo]] = {
50 | val query = openIDInfoQuery(loginInfo).joinLeft(slickOpenIDAttributes).on(_.id === _.id)
51 | val result = db.run(query.result)
52 | result.map { openIDInfos =>
53 | if (openIDInfos.isEmpty) None
54 | else {
55 | val attrs = openIDInfos.collect { case (id, Some(attr)) => (attr.key, attr.value) }.toMap
56 | Some(OpenIDInfo(openIDInfos.head._1.id, attrs))
57 | }
58 | }
59 | }
60 |
61 | /**
62 | * Adds new auth info for the given login info.
63 | *
64 | * @param loginInfo The login info for which the auth info should be added.
65 | * @param authInfo The auth info to add.
66 | * @return The added auth info.
67 | */
68 | def add(loginInfo: LoginInfo, authInfo: OpenIDInfo): Future[OpenIDInfo] =
69 | db.run(addAction(loginInfo, authInfo)).map(_ => authInfo)
70 |
71 | /**
72 | * Updates the auth info for the given login info.
73 | *
74 | * @param loginInfo The login info for which the auth info should be updated.
75 | * @param authInfo The auth info to update.
76 | * @return The updated auth info.
77 | */
78 | def update(loginInfo: LoginInfo, authInfo: OpenIDInfo): Future[OpenIDInfo] =
79 | db.run(updateAction(loginInfo, authInfo)).map(_ => authInfo)
80 |
81 | /**
82 | * Saves the auth info for the given login info.
83 | *
84 | * This method either adds the auth info if it doesn't exists or it updates the auth info
85 | * if it already exists.
86 | *
87 | * @param loginInfo The login info for which the auth info should be saved.
88 | * @param authInfo The auth info to save.
89 | * @return The saved auth info.
90 | */
91 | def save(loginInfo: LoginInfo, authInfo: OpenIDInfo): Future[OpenIDInfo] = {
92 | val query = loginInfoQuery(loginInfo).joinLeft(slickOpenIDInfos).on(_.id === _.loginInfoId)
93 | val action = query.result.head.flatMap {
94 | case (dbLoginInfo, Some(dbOpenIDInfo)) => updateAction(loginInfo, authInfo)
95 | case (dbLoginInfo, None) => addAction(loginInfo, authInfo)
96 | }
97 | db.run(action).map(_ => authInfo)
98 | }
99 |
100 | /**
101 | * Removes the auth info for the given login info.
102 | *
103 | * @param loginInfo The login info for which the auth info should be removed.
104 | * @return A future to wait for the process to be completed.
105 | */
106 | def remove(loginInfo: LoginInfo): Future[Unit] = {
107 | // val attributeQuery = for {
108 | // dbOpenIDInfo <- openIDInfoQuery(loginInfo)
109 | // dbOpenIDAttributes <- slickOpenIDAttributes.filter(_.id === dbOpenIDInfo.id)
110 | //} yield dbOpenIDAttributes
111 | // Use subquery workaround instead of join because slick only supports selecting
112 | // from a single table for update/delete queries (https://github.com/slick/slick/issues/684).
113 | val openIDInfoSubQuery = slickOpenIDInfos.filter(_.loginInfoId in loginInfoQuery(loginInfo).map(_.id))
114 | val attributeSubQuery = slickOpenIDAttributes.filter(_.id in openIDInfoSubQuery.map(_.id))
115 | db.run((openIDInfoSubQuery.delete andThen attributeSubQuery.delete).transactionally).map(_ => ())
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/app/models/daos/DBTableDefinitions.scala:
--------------------------------------------------------------------------------
1 | package models.daos
2 |
3 | import com.mohiva.play.silhouette.api.LoginInfo
4 | import slick.driver.JdbcProfile
5 | import slick.lifted.ProvenShape.proveShapeOf
6 |
7 | trait DBTableDefinitions {
8 |
9 | protected val driver: JdbcProfile
10 | import driver.api._
11 |
12 | case class DBUser (
13 | userID: String,
14 | firstName: Option[String],
15 | lastName: Option[String],
16 | fullName: Option[String],
17 | email: Option[String],
18 | avatarURL: Option[String]
19 | )
20 |
21 | class Users(tag: Tag) extends Table[DBUser](tag, "user") {
22 | def id = column[String]("userID", O.PrimaryKey)
23 | def firstName = column[Option[String]]("firstName")
24 | def lastName = column[Option[String]]("lastName")
25 | def fullName = column[Option[String]]("fullName")
26 | def email = column[Option[String]]("email")
27 | def avatarURL = column[Option[String]]("avatarURL")
28 | def * = (id, firstName, lastName, fullName, email, avatarURL) <> (DBUser.tupled, DBUser.unapply)
29 | }
30 |
31 | case class DBLoginInfo (
32 | id: Option[Long],
33 | providerID: String,
34 | providerKey: String
35 | )
36 |
37 | class LoginInfos(tag: Tag) extends Table[DBLoginInfo](tag, "logininfo") {
38 | def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
39 | def providerID = column[String]("providerID")
40 | def providerKey = column[String]("providerKey")
41 | def * = (id.?, providerID, providerKey) <> (DBLoginInfo.tupled, DBLoginInfo.unapply)
42 | }
43 |
44 | case class DBUserLoginInfo (
45 | userID: String,
46 | loginInfoId: Long
47 | )
48 |
49 | class UserLoginInfos(tag: Tag) extends Table[DBUserLoginInfo](tag, "userlogininfo") {
50 | def userID = column[String]("userID")
51 | def loginInfoId = column[Long]("loginInfoId")
52 | def * = (userID, loginInfoId) <> (DBUserLoginInfo.tupled, DBUserLoginInfo.unapply)
53 | }
54 |
55 | case class DBPasswordInfo (
56 | hasher: String,
57 | password: String,
58 | salt: Option[String],
59 | loginInfoId: Long
60 | )
61 |
62 | class PasswordInfos(tag: Tag) extends Table[DBPasswordInfo](tag, "passwordinfo") {
63 | def hasher = column[String]("hasher")
64 | def password = column[String]("password")
65 | def salt = column[Option[String]]("salt")
66 | def loginInfoId = column[Long]("loginInfoId")
67 | def * = (hasher, password, salt, loginInfoId) <> (DBPasswordInfo.tupled, DBPasswordInfo.unapply)
68 | }
69 |
70 | case class DBOAuth1Info (
71 | id: Option[Long],
72 | token: String,
73 | secret: String,
74 | loginInfoId: Long
75 | )
76 |
77 | class OAuth1Infos(tag: Tag) extends Table[DBOAuth1Info](tag, "oauth1info") {
78 | def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
79 | def token = column[String]("token")
80 | def secret = column[String]("secret")
81 | def loginInfoId = column[Long]("loginInfoId")
82 | def * = (id.?, token, secret, loginInfoId) <> (DBOAuth1Info.tupled, DBOAuth1Info.unapply)
83 | }
84 |
85 | case class DBOAuth2Info (
86 | id: Option[Long],
87 | accessToken: String,
88 | tokenType: Option[String],
89 | expiresIn: Option[Int],
90 | refreshToken: Option[String],
91 | loginInfoId: Long
92 | )
93 |
94 | class OAuth2Infos(tag: Tag) extends Table[DBOAuth2Info](tag, "oauth2info") {
95 | def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
96 | def accessToken = column[String]("accesstoken")
97 | def tokenType = column[Option[String]]("tokentype")
98 | def expiresIn = column[Option[Int]]("expiresin")
99 | def refreshToken = column[Option[String]]("refreshtoken")
100 | def loginInfoId = column[Long]("logininfoid")
101 | def * = (id.?, accessToken, tokenType, expiresIn, refreshToken, loginInfoId) <> (DBOAuth2Info.tupled, DBOAuth2Info.unapply)
102 | }
103 |
104 | case class DBOpenIDInfo (
105 | id: String,
106 | loginInfoId: Long
107 | )
108 |
109 | class OpenIDInfos(tag: Tag) extends Table[DBOpenIDInfo](tag, "openidinfo") {
110 | def id = column[String]("id", O.PrimaryKey)
111 | def loginInfoId = column[Long]("logininfoid")
112 | def * = (id, loginInfoId) <> (DBOpenIDInfo.tupled, DBOpenIDInfo.unapply)
113 | }
114 |
115 | case class DBOpenIDAttribute (
116 | id: String,
117 | key: String,
118 | value: String
119 | )
120 |
121 | class OpenIDAttributes(tag: Tag) extends Table[DBOpenIDAttribute](tag, "openidattributes") {
122 | def id = column[String]("id")
123 | def key = column[String]("key")
124 | def value = column[String]("value")
125 | def * = (id, key, value) <> (DBOpenIDAttribute.tupled, DBOpenIDAttribute.unapply)
126 | }
127 |
128 | // table query definitions
129 | val slickUsers = TableQuery[Users]
130 | val slickLoginInfos = TableQuery[LoginInfos]
131 | val slickUserLoginInfos = TableQuery[UserLoginInfos]
132 | val slickPasswordInfos = TableQuery[PasswordInfos]
133 | val slickOAuth1Infos = TableQuery[OAuth1Infos]
134 | val slickOAuth2Infos = TableQuery[OAuth2Infos]
135 | val slickOpenIDInfos = TableQuery[OpenIDInfos]
136 | val slickOpenIDAttributes = TableQuery[OpenIDAttributes]
137 |
138 | // queries used in multiple places
139 | def loginInfoQuery(loginInfo: LoginInfo) =
140 | slickLoginInfos.filter(dbLoginInfo => dbLoginInfo.providerID === loginInfo.providerID && dbLoginInfo.providerKey === loginInfo.providerKey)
141 | }
142 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/app/modules/SilhouetteModule.scala:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import com.google.inject.{ AbstractModule, Provides }
4 | import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
5 | import com.mohiva.play.silhouette.api.services._
6 | import com.mohiva.play.silhouette.api.util._
7 | import com.mohiva.play.silhouette.api.{ Environment, EventBus }
8 | import com.mohiva.play.silhouette.impl.authenticators._
9 | import com.mohiva.play.silhouette.impl.daos.DelegableAuthInfoDAO
10 | import com.mohiva.play.silhouette.impl.providers._
11 | import com.mohiva.play.silhouette.impl.providers.oauth1._
12 | import com.mohiva.play.silhouette.impl.providers.oauth1.secrets.{ CookieSecretProvider, CookieSecretSettings }
13 | import com.mohiva.play.silhouette.impl.providers.oauth1.services.PlayOAuth1Service
14 | import com.mohiva.play.silhouette.impl.providers.oauth2._
15 | import com.mohiva.play.silhouette.impl.providers.oauth2.state.{ CookieStateProvider, CookieStateSettings, DummyStateProvider }
16 | import com.mohiva.play.silhouette.impl.providers.openid.YahooProvider
17 | import com.mohiva.play.silhouette.impl.providers.openid.services.PlayOpenIDService
18 | import com.mohiva.play.silhouette.impl.repositories.DelegableAuthInfoRepository
19 | import com.mohiva.play.silhouette.impl.services._
20 | import com.mohiva.play.silhouette.impl.util._
21 | import models.User
22 | import models.daos._
23 | import models.services.{ UserService, UserServiceImpl }
24 | import net.ceedubs.ficus.Ficus._
25 | import net.ceedubs.ficus.readers.ArbitraryTypeReader._
26 | import net.codingwell.scalaguice.ScalaModule
27 | import play.api.Configuration
28 | import play.api.libs.concurrent.Execution.Implicits._
29 | import play.api.libs.openid.OpenIdClient
30 | import play.api.libs.ws.WSClient
31 |
32 | /**
33 | * The Guice module which wires all Silhouette dependencies.
34 | */
35 | class SilhouetteModule extends AbstractModule with ScalaModule {
36 |
37 | /**
38 | * Configures the module.
39 | */
40 | def configure() {
41 | bind[UserService].to[UserServiceImpl]
42 | bind[UserDAO].to[UserDAOImpl]
43 | bind[DelegableAuthInfoDAO[PasswordInfo]].to[PasswordInfoDAO]
44 | bind[DelegableAuthInfoDAO[OAuth1Info]].to[OAuth1InfoDAO]
45 | bind[DelegableAuthInfoDAO[OAuth2Info]].to[OAuth2InfoDAO]
46 | bind[DelegableAuthInfoDAO[OpenIDInfo]].to[OpenIDInfoDAO]
47 | bind[CacheLayer].to[PlayCacheLayer]
48 | bind[IDGenerator].toInstance(new SecureRandomIDGenerator())
49 | bind[PasswordHasher].toInstance(new BCryptPasswordHasher)
50 | bind[FingerprintGenerator].toInstance(new DefaultFingerprintGenerator(false))
51 | bind[EventBus].toInstance(EventBus())
52 | bind[Clock].toInstance(Clock())
53 | }
54 |
55 | /**
56 | * Provides the HTTP layer implementation.
57 | *
58 | * @param client Play's WS client.
59 | * @return The HTTP layer implementation.
60 | */
61 | @Provides
62 | def provideHTTPLayer(client: WSClient): HTTPLayer = new PlayHTTPLayer(client)
63 |
64 | /**
65 | * Provides the Silhouette environment.
66 | *
67 | * @param userService The user service implementation.
68 | * @param authenticatorService The authentication service implementation.
69 | * @param eventBus The event bus instance.
70 | * @return The Silhouette environment.
71 | */
72 | @Provides
73 | def provideEnvironment(
74 | userService: UserService,
75 | authenticatorService: AuthenticatorService[CookieAuthenticator],
76 | eventBus: EventBus): Environment[User, CookieAuthenticator] = {
77 |
78 | Environment[User, CookieAuthenticator](
79 | userService,
80 | authenticatorService,
81 | Seq(),
82 | eventBus
83 | )
84 | }
85 |
86 | /**
87 | * Provides the social provider registry.
88 | *
89 | * @param facebookProvider The Facebook provider implementation.
90 | * @param googleProvider The Google provider implementation.
91 | * @param vkProvider The VK provider implementation.
92 | * @param clefProvider The Clef provider implementation.
93 | * @param twitterProvider The Twitter provider implementation.
94 | * @param xingProvider The Xing provider implementation.
95 | * @param yahooProvider The Yahoo provider implementation.
96 | * @return The Silhouette environment.
97 | */
98 | @Provides
99 | def provideSocialProviderRegistry(
100 | facebookProvider: FacebookProvider,
101 | googleProvider: GoogleProvider,
102 | vkProvider: VKProvider,
103 | clefProvider: ClefProvider,
104 | twitterProvider: TwitterProvider,
105 | xingProvider: XingProvider,
106 | yahooProvider: YahooProvider): SocialProviderRegistry = {
107 |
108 | SocialProviderRegistry(Seq(
109 | googleProvider,
110 | facebookProvider,
111 | twitterProvider,
112 | vkProvider,
113 | xingProvider,
114 | yahooProvider,
115 | clefProvider
116 | ))
117 | }
118 |
119 | /**
120 | * Provides the authenticator service.
121 | *
122 | * @param fingerprintGenerator The fingerprint generator implementation.
123 | * @param idGenerator The ID generator implementation.
124 | * @param configuration The Play configuration.
125 | * @param clock The clock instance.
126 | * @return The authenticator service.
127 | */
128 | @Provides
129 | def provideAuthenticatorService(
130 | fingerprintGenerator: FingerprintGenerator,
131 | idGenerator: IDGenerator,
132 | configuration: Configuration,
133 | clock: Clock): AuthenticatorService[CookieAuthenticator] = {
134 |
135 | val config = configuration.underlying.as[CookieAuthenticatorSettings]("silhouette.authenticator")
136 | new CookieAuthenticatorService(config, None, fingerprintGenerator, idGenerator, clock)
137 | }
138 |
139 | /**
140 | * Provides the auth info repository.
141 | *
142 | * @param passwordInfoDAO The implementation of the delegable password auth info DAO.
143 | * @param oauth1InfoDAO The implementation of the delegable OAuth1 auth info DAO.
144 | * @param oauth2InfoDAO The implementation of the delegable OAuth2 auth info DAO.
145 | * @param openIDInfoDAO The implementation of the delegable OpenID auth info DAO.
146 | * @return The auth info repository instance.
147 | */
148 | @Provides
149 | def provideAuthInfoRepository(
150 | passwordInfoDAO: DelegableAuthInfoDAO[PasswordInfo],
151 | oauth1InfoDAO: DelegableAuthInfoDAO[OAuth1Info],
152 | oauth2InfoDAO: DelegableAuthInfoDAO[OAuth2Info],
153 | openIDInfoDAO: DelegableAuthInfoDAO[OpenIDInfo]): AuthInfoRepository = {
154 |
155 | new DelegableAuthInfoRepository(passwordInfoDAO, oauth1InfoDAO, oauth2InfoDAO, openIDInfoDAO)
156 | }
157 |
158 | /**
159 | * Provides the avatar service.
160 | *
161 | * @param httpLayer The HTTP layer implementation.
162 | * @return The avatar service implementation.
163 | */
164 | @Provides
165 | def provideAvatarService(httpLayer: HTTPLayer): AvatarService = new GravatarService(httpLayer)
166 |
167 | /**
168 | * Provides the OAuth1 token secret provider.
169 | *
170 | * @param configuration The Play configuration.
171 | * @param clock The clock instance.
172 | * @return The OAuth1 token secret provider implementation.
173 | */
174 | @Provides
175 | def provideOAuth1TokenSecretProvider(configuration: Configuration, clock: Clock): OAuth1TokenSecretProvider = {
176 | val settings = configuration.underlying.as[CookieSecretSettings]("silhouette.oauth1TokenSecretProvider")
177 | new CookieSecretProvider(settings, clock)
178 | }
179 |
180 | /**
181 | * Provides the OAuth2 state provider.
182 | *
183 | * @param idGenerator The ID generator implementation.
184 | * @param configuration The Play configuration.
185 | * @param clock The clock instance.
186 | * @return The OAuth2 state provider implementation.
187 | */
188 | @Provides
189 | def provideOAuth2StateProvider(idGenerator: IDGenerator, configuration: Configuration, clock: Clock): OAuth2StateProvider = {
190 | val settings = configuration.underlying.as[CookieStateSettings]("silhouette.oauth2StateProvider")
191 | new CookieStateProvider(settings, idGenerator, clock)
192 | }
193 |
194 | /**
195 | * Provides the credentials provider.
196 | *
197 | * @param authInfoRepository The auth info repository implementation.
198 | * @param passwordHasher The default password hasher implementation.
199 | * @return The credentials provider.
200 | */
201 | @Provides
202 | def provideCredentialsProvider(
203 | authInfoRepository: AuthInfoRepository,
204 | passwordHasher: PasswordHasher): CredentialsProvider = {
205 |
206 | new CredentialsProvider(authInfoRepository, passwordHasher, Seq(passwordHasher))
207 | }
208 |
209 | /**
210 | * Provides the Facebook provider.
211 | *
212 | * @param httpLayer The HTTP layer implementation.
213 | * @param stateProvider The OAuth2 state provider implementation.
214 | * @param configuration The Play configuration.
215 | * @return The Facebook provider.
216 | */
217 | @Provides
218 | def provideFacebookProvider(
219 | httpLayer: HTTPLayer,
220 | stateProvider: OAuth2StateProvider,
221 | configuration: Configuration): FacebookProvider = {
222 |
223 | new FacebookProvider(httpLayer, stateProvider, configuration.underlying.as[OAuth2Settings]("silhouette.facebook"))
224 | }
225 |
226 | /**
227 | * Provides the Google provider.
228 | *
229 | * @param httpLayer The HTTP layer implementation.
230 | * @param stateProvider The OAuth2 state provider implementation.
231 | * @param configuration The Play configuration.
232 | * @return The Google provider.
233 | */
234 | @Provides
235 | def provideGoogleProvider(
236 | httpLayer: HTTPLayer,
237 | stateProvider: OAuth2StateProvider,
238 | configuration: Configuration): GoogleProvider = {
239 |
240 | new GoogleProvider(httpLayer, stateProvider, configuration.underlying.as[OAuth2Settings]("silhouette.google"))
241 | }
242 |
243 | /**
244 | * Provides the VK provider.
245 | *
246 | * @param httpLayer The HTTP layer implementation.
247 | * @param stateProvider The OAuth2 state provider implementation.
248 | * @param configuration The Play configuration.
249 | * @return The VK provider.
250 | */
251 | @Provides
252 | def provideVKProvider(
253 | httpLayer: HTTPLayer,
254 | stateProvider: OAuth2StateProvider,
255 | configuration: Configuration): VKProvider = {
256 |
257 | new VKProvider(httpLayer, stateProvider, configuration.underlying.as[OAuth2Settings]("silhouette.vk"))
258 | }
259 |
260 | /**
261 | * Provides the Clef provider.
262 | *
263 | * @param httpLayer The HTTP layer implementation.
264 | * @param configuration The Play configuration.
265 | * @return The Clef provider.
266 | */
267 | @Provides
268 | def provideClefProvider(httpLayer: HTTPLayer, configuration: Configuration): ClefProvider = {
269 |
270 | new ClefProvider(httpLayer, new DummyStateProvider, configuration.underlying.as[OAuth2Settings]("silhouette.clef"))
271 | }
272 |
273 | /**
274 | * Provides the Twitter provider.
275 | *
276 | * @param httpLayer The HTTP layer implementation.
277 | * @param tokenSecretProvider The token secret provider implementation.
278 | * @param configuration The Play configuration.
279 | * @return The Twitter provider.
280 | */
281 | @Provides
282 | def provideTwitterProvider(
283 | httpLayer: HTTPLayer,
284 | tokenSecretProvider: OAuth1TokenSecretProvider,
285 | configuration: Configuration): TwitterProvider = {
286 |
287 | val settings = configuration.underlying.as[OAuth1Settings]("silhouette.twitter")
288 | new TwitterProvider(httpLayer, new PlayOAuth1Service(settings), tokenSecretProvider, settings)
289 | }
290 |
291 | /**
292 | * Provides the Xing provider.
293 | *
294 | * @param httpLayer The HTTP layer implementation.
295 | * @param tokenSecretProvider The token secret provider implementation.
296 | * @param configuration The Play configuration.
297 | * @return The Xing provider.
298 | */
299 | @Provides
300 | def provideXingProvider(
301 | httpLayer: HTTPLayer,
302 | tokenSecretProvider: OAuth1TokenSecretProvider,
303 | configuration: Configuration): XingProvider = {
304 |
305 | val settings = configuration.underlying.as[OAuth1Settings]("silhouette.xing")
306 | new XingProvider(httpLayer, new PlayOAuth1Service(settings), tokenSecretProvider, settings)
307 | }
308 |
309 | /**
310 | * Provides the Yahoo provider.
311 | *
312 | * @param cacheLayer The cache layer implementation.
313 | * @param httpLayer The HTTP layer implementation.
314 | * @param client The OpenID client implementation.
315 | * @param configuration The Play configuration.
316 | * @return The Yahoo provider.
317 | */
318 | @Provides
319 | def provideYahooProvider(
320 | cacheLayer: CacheLayer,
321 | httpLayer: HTTPLayer,
322 | client: OpenIdClient,
323 | configuration: Configuration): YahooProvider = {
324 |
325 | val settings = configuration.underlying.as[OpenIDSettings]("silhouette.yahoo")
326 | new YahooProvider(httpLayer, new PlayOpenIDService(client, settings), settings)
327 | }
328 | }
329 |
--------------------------------------------------------------------------------
/scripts/sbt:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # A more capable sbt runner, coincidentally also called sbt.
4 | # Author: Paul Phillips
5 |
6 | # todo - make this dynamic
7 | declare -r sbt_release_version="0.13.8"
8 | declare -r sbt_unreleased_version="0.13.8"
9 | declare -r buildProps="project/build.properties"
10 |
11 | declare sbt_jar sbt_dir sbt_create sbt_version
12 | declare scala_version sbt_explicit_version
13 | declare verbose noshare batch trace_level log_level
14 | declare sbt_saved_stty debugUs
15 |
16 | echoerr () { echo >&2 "$@"; }
17 | vlog () { [[ -n "$verbose" ]] && echoerr "$@"; }
18 |
19 | # spaces are possible, e.g. sbt.version = 0.13.0
20 | build_props_sbt () {
21 | [[ -r "$buildProps" ]] && \
22 | grep '^sbt\.version' "$buildProps" | tr '=\r' ' ' | awk '{ print $2; }'
23 | }
24 |
25 | update_build_props_sbt () {
26 | local ver="$1"
27 | local old="$(build_props_sbt)"
28 |
29 | [[ -r "$buildProps" ]] && [[ "$ver" != "$old" ]] && {
30 | perl -pi -e "s/^sbt\.version\b.*\$/sbt.version=${ver}/" "$buildProps"
31 | grep -q '^sbt.version[ =]' "$buildProps" || printf "\nsbt.version=%s\n" "$ver" >> "$buildProps"
32 |
33 | vlog "!!!"
34 | vlog "!!! Updated file $buildProps setting sbt.version to: $ver"
35 | vlog "!!! Previous value was: $old"
36 | vlog "!!!"
37 | }
38 | }
39 |
40 | set_sbt_version () {
41 | sbt_version="${sbt_explicit_version:-$(build_props_sbt)}"
42 | [[ -n "$sbt_version" ]] || sbt_version=$sbt_release_version
43 | export sbt_version
44 | }
45 |
46 | # restore stty settings (echo in particular)
47 | onSbtRunnerExit() {
48 | [[ -n "$sbt_saved_stty" ]] || return
49 | vlog ""
50 | vlog "restoring stty: $sbt_saved_stty"
51 | stty "$sbt_saved_stty"
52 | unset sbt_saved_stty
53 | }
54 |
55 | # save stty and trap exit, to ensure echo is reenabled if we are interrupted.
56 | trap onSbtRunnerExit EXIT
57 | sbt_saved_stty="$(stty -g 2>/dev/null)"
58 | vlog "Saved stty: $sbt_saved_stty"
59 |
60 | # this seems to cover the bases on OSX, and someone will
61 | # have to tell me about the others.
62 | get_script_path () {
63 | local path="$1"
64 | [[ -L "$path" ]] || { echo "$path" ; return; }
65 |
66 | local target="$(readlink "$path")"
67 | if [[ "${target:0:1}" == "/" ]]; then
68 | echo "$target"
69 | else
70 | echo "${path%/*}/$target"
71 | fi
72 | }
73 |
74 | die() {
75 | echo "Aborting: $@"
76 | exit 1
77 | }
78 |
79 | make_url () {
80 | version="$1"
81 |
82 | case "$version" in
83 | 0.7.*) echo "http://simple-build-tool.googlecode.com/files/sbt-launch-0.7.7.jar" ;;
84 | 0.10.* ) echo "$sbt_launch_repo/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;;
85 | 0.11.[12]) echo "$sbt_launch_repo/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;;
86 | *) echo "$sbt_launch_repo/org.scala-sbt/sbt-launch/$version/sbt-launch.jar" ;;
87 | esac
88 | }
89 |
90 | init_default_option_file () {
91 | local overriding_var="${!1}"
92 | local default_file="$2"
93 | if [[ ! -r "$default_file" && "$overriding_var" =~ ^@(.*)$ ]]; then
94 | local envvar_file="${BASH_REMATCH[1]}"
95 | if [[ -r "$envvar_file" ]]; then
96 | default_file="$envvar_file"
97 | fi
98 | fi
99 | echo "$default_file"
100 | }
101 |
102 | declare -r cms_opts="-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC"
103 | declare -r jit_opts="-XX:ReservedCodeCacheSize=256m -XX:+TieredCompilation"
104 | declare -r default_jvm_opts_common="-Xms512m -Xmx1536m -Xss2m $jit_opts $cms_opts"
105 | declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy"
106 | declare -r latest_28="2.8.2"
107 | declare -r latest_29="2.9.3"
108 | declare -r latest_210="2.10.5"
109 | declare -r latest_211="2.11.6"
110 |
111 | declare -r script_path="$(get_script_path "$BASH_SOURCE")"
112 | declare -r script_name="${script_path##*/}"
113 |
114 | # some non-read-onlies set with defaults
115 | declare java_cmd="java"
116 | declare sbt_opts_file="$(init_default_option_file SBT_OPTS .sbtopts)"
117 | declare jvm_opts_file="$(init_default_option_file JVM_OPTS .jvmopts)"
118 | declare sbt_launch_repo="http://typesafe.artifactoryonline.com/typesafe/ivy-releases"
119 |
120 | # pull -J and -D options to give to java.
121 | declare -a residual_args
122 | declare -a java_args
123 | declare -a scalac_args
124 | declare -a sbt_commands
125 |
126 | # args to jvm/sbt via files or environment variables
127 | declare -a extra_jvm_opts extra_sbt_opts
128 |
129 | addJava () {
130 | vlog "[addJava] arg = '$1'"
131 | java_args=( "${java_args[@]}" "$1" )
132 | }
133 | addSbt () {
134 | vlog "[addSbt] arg = '$1'"
135 | sbt_commands=( "${sbt_commands[@]}" "$1" )
136 | }
137 | setThisBuild () {
138 | vlog "[addBuild] args = '$@'"
139 | local key="$1" && shift
140 | addSbt "set $key in ThisBuild := $@"
141 | }
142 | addScalac () {
143 | vlog "[addScalac] arg = '$1'"
144 | scalac_args=( "${scalac_args[@]}" "$1" )
145 | }
146 | addResidual () {
147 | vlog "[residual] arg = '$1'"
148 | residual_args=( "${residual_args[@]}" "$1" )
149 | }
150 | addResolver () {
151 | addSbt "set resolvers += $1"
152 | }
153 | addDebugger () {
154 | addJava "-Xdebug"
155 | addJava "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1"
156 | }
157 | setScalaVersion () {
158 | [[ "$1" == *"-SNAPSHOT" ]] && addResolver 'Resolver.sonatypeRepo("snapshots")'
159 | addSbt "++ $1"
160 | }
161 | setJavaHome () {
162 | java_cmd="$1/bin/java"
163 | setThisBuild javaHome "Some(file(\"$1\"))"
164 | export JAVA_HOME="$1"
165 | export JDK_HOME="$1"
166 | export PATH="$JAVA_HOME/bin:$PATH"
167 | }
168 | setJavaHomeQuietly () {
169 | java_cmd="$1/bin/java"
170 | addSbt ";warn ;set javaHome in ThisBuild := Some(file(\"$1\")) ;info"
171 | export JAVA_HOME="$1"
172 | export JDK_HOME="$1"
173 | export PATH="$JAVA_HOME/bin:$PATH"
174 | }
175 |
176 | # if set, use JDK_HOME/JAVA_HOME over java found in path
177 | if [[ -e "$JDK_HOME/lib/tools.jar" ]]; then
178 | setJavaHomeQuietly "$JDK_HOME"
179 | elif [[ -e "$JAVA_HOME/bin/java" ]]; then
180 | setJavaHomeQuietly "$JAVA_HOME"
181 | fi
182 |
183 | # directory to store sbt launchers
184 | declare sbt_launch_dir="$HOME/.sbt/launchers"
185 | [[ -d "$sbt_launch_dir" ]] || mkdir -p "$sbt_launch_dir"
186 | [[ -w "$sbt_launch_dir" ]] || sbt_launch_dir="$(mktemp -d -t sbt_extras_launchers.XXXXXX)"
187 |
188 | java_version () {
189 | local version=$("$java_cmd" -version 2>&1 | grep -E -e '(java|openjdk) version' | awk '{ print $3 }' | tr -d \")
190 | vlog "Detected Java version: $version"
191 | echo "${version:2:1}"
192 | }
193 |
194 | # MaxPermSize critical on pre-8 jvms but incurs noisy warning on 8+
195 | default_jvm_opts () {
196 | local v="$(java_version)"
197 | if [[ $v -ge 8 ]]; then
198 | echo "$default_jvm_opts_common"
199 | else
200 | echo "-XX:MaxPermSize=384m $default_jvm_opts_common"
201 | fi
202 | }
203 |
204 | build_props_scala () {
205 | if [[ -r "$buildProps" ]]; then
206 | versionLine="$(grep '^build.scala.versions' "$buildProps")"
207 | versionString="${versionLine##build.scala.versions=}"
208 | echo "${versionString%% .*}"
209 | fi
210 | }
211 |
212 | execRunner () {
213 | # print the arguments one to a line, quoting any containing spaces
214 | vlog "# Executing command line:" && {
215 | for arg; do
216 | if [[ -n "$arg" ]]; then
217 | if printf "%s\n" "$arg" | grep -q ' '; then
218 | printf >&2 "\"%s\"\n" "$arg"
219 | else
220 | printf >&2 "%s\n" "$arg"
221 | fi
222 | fi
223 | done
224 | vlog ""
225 | }
226 |
227 | [[ -n "$batch" ]] && exec /dev/null; then
249 | curl --fail --silent "$url" --output "$jar"
250 | elif which wget >/dev/null; then
251 | wget --quiet -O "$jar" "$url"
252 | fi
253 | } && [[ -r "$jar" ]]
254 | }
255 |
256 | acquire_sbt_jar () {
257 | sbt_url="$(jar_url "$sbt_version")"
258 | sbt_jar="$(jar_file "$sbt_version")"
259 |
260 | [[ -r "$sbt_jar" ]] || download_url "$sbt_url" "$sbt_jar"
261 | }
262 |
263 | usage () {
264 | cat < display stack traces with a max of frames (default: -1, traces suppressed)
283 | -debug-inc enable debugging log for the incremental compiler
284 | -no-colors disable ANSI color codes
285 | -sbt-create start sbt even if current directory contains no sbt project
286 | -sbt-dir path to global settings/plugins directory (default: ~/.sbt/)
287 | -sbt-boot path to shared boot directory (default: ~/.sbt/boot in 0.11+)
288 | -ivy path to local Ivy repository (default: ~/.ivy2)
289 | -no-share use all local caches; no sharing
290 | -offline put sbt in offline mode
291 | -jvm-debug Turn on JVM debugging, open at the given port.
292 | -batch Disable interactive mode
293 | -prompt Set the sbt prompt; in expr, 's' is the State and 'e' is Extracted
294 |
295 | # sbt version (default: sbt.version from $buildProps if present, otherwise $sbt_release_version)
296 | -sbt-force-latest force the use of the latest release of sbt: $sbt_release_version
297 | -sbt-version use the specified version of sbt (default: $sbt_release_version)
298 | -sbt-dev use the latest pre-release version of sbt: $sbt_unreleased_version
299 | -sbt-jar use the specified jar as the sbt launcher
300 | -sbt-launch-dir directory to hold sbt launchers (default: ~/.sbt/launchers)
301 | -sbt-launch-repo repo url for downloading sbt launcher jar (default: $sbt_launch_repo)
302 |
303 | # scala version (default: as chosen by sbt)
304 | -28 use $latest_28
305 | -29 use $latest_29
306 | -210 use $latest_210
307 | -211 use $latest_211
308 | -scala-home use the scala build at the specified directory
309 | -scala-version use the specified version of scala
310 | -binary-version use the specified scala version when searching for dependencies
311 |
312 | # java version (default: java from PATH, currently $(java -version 2>&1 | grep version))
313 | -java-home alternate JAVA_HOME
314 |
315 | # passing options to the jvm - note it does NOT use JAVA_OPTS due to pollution
316 | # The default set is used if JVM_OPTS is unset and no -jvm-opts file is found
317 | $(default_jvm_opts)
318 | JVM_OPTS environment variable holding either the jvm args directly, or
319 | the reference to a file containing jvm args if given path is prepended by '@' (e.g. '@/etc/jvmopts')
320 | Note: "@"-file is overridden by local '.jvmopts' or '-jvm-opts' argument.
321 | -jvm-opts file containing jvm args (if not given, .jvmopts in project root is used if present)
322 | -Dkey=val pass -Dkey=val directly to the jvm
323 | -J-X pass option -X directly to the jvm (-J is stripped)
324 |
325 | # passing options to sbt, OR to this runner
326 | SBT_OPTS environment variable holding either the sbt args directly, or
327 | the reference to a file containing sbt args if given path is prepended by '@' (e.g. '@/etc/sbtopts')
328 | Note: "@"-file is overridden by local '.sbtopts' or '-sbt-opts' argument.
329 | -sbt-opts file containing sbt args (if not given, .sbtopts in project root is used if present)
330 | -S-X add -X to sbt's scalacOptions (-S is stripped)
331 | EOM
332 | }
333 |
334 | process_args ()
335 | {
336 | require_arg () {
337 | local type="$1"
338 | local opt="$2"
339 | local arg="$3"
340 |
341 | if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then
342 | die "$opt requires <$type> argument"
343 | fi
344 | }
345 | while [[ $# -gt 0 ]]; do
346 | case "$1" in
347 | -h|-help) usage; exit 1 ;;
348 | -v) verbose=true && shift ;;
349 | -d) addSbt "--debug" && addSbt debug && shift ;;
350 | -w) addSbt "--warn" && addSbt warn && shift ;;
351 | -q) addSbt "--error" && addSbt error && shift ;;
352 | -x) debugUs=true && shift ;;
353 | -trace) require_arg integer "$1" "$2" && trace_level="$2" && shift 2 ;;
354 | -ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;;
355 | -no-colors) addJava "-Dsbt.log.noformat=true" && shift ;;
356 | -no-share) noshare=true && shift ;;
357 | -sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;;
358 | -sbt-dir) require_arg path "$1" "$2" && sbt_dir="$2" && shift 2 ;;
359 | -debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;;
360 | -offline) addSbt "set offline := true" && shift ;;
361 | -jvm-debug) require_arg port "$1" "$2" && addDebugger "$2" && shift 2 ;;
362 | -batch) batch=true && shift ;;
363 | -prompt) require_arg "expr" "$1" "$2" && setThisBuild shellPrompt "(s => { val e = Project.extract(s) ; $2 })" && shift 2 ;;
364 |
365 | -sbt-create) sbt_create=true && shift ;;
366 | -sbt-jar) require_arg path "$1" "$2" && sbt_jar="$2" && shift 2 ;;
367 | -sbt-version) require_arg version "$1" "$2" && sbt_explicit_version="$2" && shift 2 ;;
368 | -sbt-force-latest) sbt_explicit_version="$sbt_release_version" && shift ;;
369 | -sbt-dev) sbt_explicit_version="$sbt_unreleased_version" && shift ;;
370 | -sbt-launch-dir) require_arg path "$1" "$2" && sbt_launch_dir="$2" && shift 2 ;;
371 | -sbt-launch-repo) require_arg path "$1" "$2" && sbt_launch_repo="$2" && shift 2 ;;
372 | -scala-version) require_arg version "$1" "$2" && setScalaVersion "$2" && shift 2 ;;
373 | -binary-version) require_arg version "$1" "$2" && setThisBuild scalaBinaryVersion "\"$2\"" && shift 2 ;;
374 | -scala-home) require_arg path "$1" "$2" && setThisBuild scalaHome "Some(file(\"$2\"))" && shift 2 ;;
375 | -java-home) require_arg path "$1" "$2" && setJavaHome "$2" && shift 2 ;;
376 | -sbt-opts) require_arg path "$1" "$2" && sbt_opts_file="$2" && shift 2 ;;
377 | -jvm-opts) require_arg path "$1" "$2" && jvm_opts_file="$2" && shift 2 ;;
378 |
379 | -D*) addJava "$1" && shift ;;
380 | -J*) addJava "${1:2}" && shift ;;
381 | -S*) addScalac "${1:2}" && shift ;;
382 | -28) setScalaVersion "$latest_28" && shift ;;
383 | -29) setScalaVersion "$latest_29" && shift ;;
384 | -210) setScalaVersion "$latest_210" && shift ;;
385 | -211) setScalaVersion "$latest_211" && shift ;;
386 |
387 | --debug) addSbt debug && addResidual "$1" && shift ;;
388 | --warn) addSbt warn && addResidual "$1" && shift ;;
389 | --error) addSbt error && addResidual "$1" && shift ;;
390 | *) addResidual "$1" && shift ;;
391 | esac
392 | done
393 | }
394 |
395 | # process the direct command line arguments
396 | process_args "$@"
397 |
398 | # skip #-styled comments and blank lines
399 | readConfigFile() {
400 | while read line; do
401 | [[ $line =~ ^# ]] || [[ -z $line ]] || echo "$line"
402 | done < "$1"
403 | }
404 |
405 | # if there are file/environment sbt_opts, process again so we
406 | # can supply args to this runner
407 | if [[ -r "$sbt_opts_file" ]]; then
408 | vlog "Using sbt options defined in file $sbt_opts_file"
409 | while read opt; do extra_sbt_opts+=("$opt"); done < <(readConfigFile "$sbt_opts_file")
410 | elif [[ -n "$SBT_OPTS" && ! ("$SBT_OPTS" =~ ^@.*) ]]; then
411 | vlog "Using sbt options defined in variable \$SBT_OPTS"
412 | extra_sbt_opts=( $SBT_OPTS )
413 | else
414 | vlog "No extra sbt options have been defined"
415 | fi
416 |
417 | [[ -n "${extra_sbt_opts[*]}" ]] && process_args "${extra_sbt_opts[@]}"
418 |
419 | # reset "$@" to the residual args
420 | set -- "${residual_args[@]}"
421 | argumentCount=$#
422 |
423 | # set sbt version
424 | set_sbt_version
425 |
426 | # only exists in 0.12+
427 | setTraceLevel() {
428 | case "$sbt_version" in
429 | "0.7."* | "0.10."* | "0.11."* ) echoerr "Cannot set trace level in sbt version $sbt_version" ;;
430 | *) setThisBuild traceLevel $trace_level ;;
431 | esac
432 | }
433 |
434 | # set scalacOptions if we were given any -S opts
435 | [[ ${#scalac_args[@]} -eq 0 ]] || addSbt "set scalacOptions in ThisBuild += \"${scalac_args[@]}\""
436 |
437 | # Update build.properties on disk to set explicit version - sbt gives us no choice
438 | [[ -n "$sbt_explicit_version" ]] && update_build_props_sbt "$sbt_explicit_version"
439 | vlog "Detected sbt version $sbt_version"
440 |
441 | [[ -n "$scala_version" ]] && vlog "Overriding scala version to $scala_version"
442 |
443 | # no args - alert them there's stuff in here
444 | (( argumentCount > 0 )) || {
445 | vlog "Starting $script_name: invoke with -help for other options"
446 | residual_args=( shell )
447 | }
448 |
449 | # verify this is an sbt dir or -create was given
450 | [[ -r ./build.sbt || -d ./project || -n "$sbt_create" ]] || {
451 | cat <