Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vkontakte provider and example of usage #170

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ object ApplicationBuild extends Build {
lazy val social = Project (id = "social", base = file ("social"))
.settings(
baseSettings,
name := appName + "-social",
libraryDependencies += "com.typesafe.play" %% "play" % playVersion % "provided",
libraryDependencies += "com.typesafe.play" %% "play-ws" % playVersion % "provided",
publishMavenStyle := appPublishMavenStyle,
name := appName + "-social",
libraryDependencies += "com.typesafe.play" %% "play" % playVersion % "provided",
libraryDependencies += "com.typesafe.play" %% "play-ws" % playVersion % "provided",
publishMavenStyle := appPublishMavenStyle,
publishArtifact in Test := appPublishArtifactInTest,
pomIncludeRepository := appPomIncludeRepository,
publishTo <<=(version)(appPublishTo),
Expand Down
62 changes: 53 additions & 9 deletions social-sample/app/controllers/Application.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package controllers

import jp.t2v.lab.play2.auth.social.providers.twitter.{TwitterController, TwitterProviderUserSupport}
import jp.t2v.lab.play2.auth.social.providers.facebook.{FacebookController, FacebookProviderUserSupport}
import jp.t2v.lab.play2.auth.social.providers.github.{GitHubController, GitHubProviderUserSupport}
import jp.t2v.lab.play2.auth.social.providers.slack.SlackController
import jp.t2v.lab.play2.auth.social.providers.vkontakte.{VkontakteToken, VkontakteController, VkontakteProviderUserSupport}
import jp.t2v.lab.play2.auth._
import models._
import play.api.mvc.Results._
import play.api.mvc._
import scalikejdbc.DB
import scalikejdbc._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ ExecutionContext, Future }
import scala.reflect.{ ClassTag, classTag }
import jp.t2v.lab.play2.auth._
import jp.t2v.lab.play2.auth.social.providers.twitter.{TwitterProviderUserSupport, TwitterController}
import jp.t2v.lab.play2.auth.social.providers.facebook.{FacebookProviderUserSupport, FacebookController}
import jp.t2v.lab.play2.auth.social.providers.github.{GitHubProviderUserSupport, GitHubController}
import jp.t2v.lab.play2.auth.social.providers.slack.SlackController
import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.{ClassTag, classTag}


object Application extends Controller with OptionalAuthElement with AuthConfigImpl with Logout {

Expand All @@ -23,7 +25,8 @@ object Application extends Controller with OptionalAuthElement with AuthConfigIm
val facebookUser = user.flatMap(u => FacebookUser.findByUserId(u.id))
val twitterUser = user.flatMap(u => TwitterUser.findByUserId(u.id))
val slackAccessToken = user.flatMap(u => SlackAccessToken.findByUserId(u.id))
Ok(views.html.index(user, gitHubUser, facebookUser, twitterUser, slackAccessToken))
val vkontakteUser = user.flatMap(u => VkontakteUser.findByUserId(u.id))
Ok(views.html.index(user, gitHubUser, facebookUser, twitterUser, slackAccessToken,vkontakteUser))
}
}

Expand Down Expand Up @@ -170,3 +173,44 @@ object SlackAuthController extends SlackController
}

}


object VkontakteAuthController extends VkontakteController
with AuthConfigImpl
with VkontakteProviderUserSupport {

override type AccessToken = VkontakteToken

override def onOAuthLinkSucceeded(token: AccessToken, consumerUser: User)(implicit request: RequestHeader, ctx: ExecutionContext): Future[Result] = {
retrieveProviderUser(token).map { providerUser =>
DB.localTx { implicit session =>
VkontakteUser.save(consumerUser.id, providerUser)
Redirect(routes.Application.index)
}
}
}

override def onOAuthLoginSucceeded(token: AccessToken)(implicit request: RequestHeader, ctx: ExecutionContext): Future[Result] = {
retrieveProviderUser(token).flatMap { providerUser =>
DB.localTx { implicit session =>
VkontakteUser.findById(providerUser.id) match {
case None =>
val id = User.create(providerUser.first_name, providerUser.coverUrl).id
VkontakteUser.save(id, providerUser)
gotoLoginSucceeded(id)
case Some(fu) =>
gotoLoginSucceeded(fu.userId)
}
}
}
}


def onAuthSucces(): Unit = {

}


}


40 changes: 39 additions & 1 deletion social-sample/app/models/User.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package models

import jp.t2v.lab.play2.auth.social.providers
import jp.t2v.lab.play2.auth.social.providers.{facebook, twitter,vkontakte}
import scalikejdbc._
import jp.t2v.lab.play2.auth.social.providers.{facebook, twitter}

sealed trait Authority
case object Admin extends Authority
Expand Down Expand Up @@ -35,6 +35,15 @@ case class TwitterUser(
case class SlackAccessToken(
userId: Long,
accessToken: String)


case class VkontakteUser(
userId: Long,
id: String,
name: String,
coverUrl: String,
accessToken: String)

object User {

def *(rs: WrappedResultSet) = User(
Expand Down Expand Up @@ -165,4 +174,33 @@ object SlackAccessToken {
SlackAccessToken(userId, accessToken)
}

}

object VkontakteUser {

def *(rs: WrappedResultSet) = VkontakteUser(
rs.long("user_id"),
rs.string("id"),
rs.string("name"),
rs.string("cover_url"),
rs.string("access_token")
)

def findById(id: String)(implicit session: DBSession): Option[VkontakteUser] = {
sql"SELECT * FROM vkontakte_users WHERE id = $id".map(*).single().apply()
}

def findByUserId(userId: Long)(implicit session: DBSession): Option[VkontakteUser] = {
sql"SELECT * FROM vkontakte_users WHERE user_id = $userId".map(*).single().apply()
}

def save(userId: Long, vkontakteUser: vkontakte.VkontakteUser)(implicit session: DBSession): VkontakteUser = {
val id = vkontakteUser.id
val first_name = vkontakteUser.first_name
val coverUrl = vkontakteUser.coverUrl
val accessToken = vkontakteUser.accessToken
sql"INSERT INTO vkontakte_users(user_id, id, name, cover_url, access_token) VALUES ($userId, $id, $first_name, $coverUrl, $accessToken)".update.apply()
VkontakteUser(userId, id, first_name, coverUrl, accessToken)
}

}
21 changes: 20 additions & 1 deletion social-sample/app/views/index.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
gitHubUser: Option[GitHubUser],
facebookUser: Option[FacebookUser],
twitterUser: Option[TwitterUser],
slackAccessToken: Option[SlackAccessToken])
slackAccessToken: Option[SlackAccessToken],
vkontakteUser: Option[VkontakteUser])

<html>
<head>
Expand Down Expand Up @@ -98,6 +99,21 @@ <h2>Slack</h2>
}
</div>

<div class="provider">
<h2>Vkontakte</h2>

@vkontakteUser.map { u =>
<p>
@u.name
</p>
<p>
<img src="@{u.coverUrl}">
</p>
}.getOrElse {
<a href="@routes.VkontakteAuthController.link("email")">link Vkontakte</a>
}
</div>

</div>

}.getOrElse {
Expand All @@ -110,6 +126,9 @@ <h2>Slack</h2>
<p>
<a href="@routes.FacebookAuthController.login("email")">facebook login</a>
</p>
<p>
<a href="@routes.VkontakteAuthController.login("email")">vkontakte login</a>
</p>
}
</div>
</body>
Expand Down
4 changes: 4 additions & 0 deletions social-sample/conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ slack.clientId = ${SLACK_CLIENT_ID}
slack.clientSecret = ${SLACK_CLIENT_SECRET}
slack.callbackURL = ${SLACK_CALLBACK_URL}

vkontakte.clientId = ${VKONTAKTE_CLIENT_ID}
vkontakte.clientSecret = ${VKONTAKTE_CLIENT_SECRET}
vkontakte.callbackURL =${VKONTAKTE_CALLBACK_URL}

logger.com.github.tototoshi.play.social = DEBUG

db.default.driver=org.h2.Driver
Expand Down
8 changes: 8 additions & 0 deletions social-sample/conf/db/migration/default/V1__create_tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,12 @@ CREATE TABLE facebook_users (
CREATE TABLE slack_access_token (
user_id INTEGER NOT NULL UNIQUE REFERENCES users(id),
access_token VARCHAR(1000) NOT NULL
);

CREATE TABLE vkontakte_users (
user_id INTEGER NOT NULL UNIQUE REFERENCES users(id),
id VARCHAR(100) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
cover_url VARCHAR(1000) NOT NULL,
access_token VARCHAR(1000) NOT NULL
);
6 changes: 5 additions & 1 deletion social-sample/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ GET /link/facebook controllers.FacebookAuthController.link(sc
GET /authorize/facebook controllers.FacebookAuthController.authorize

GET /link/slack controllers.SlackAuthController.link(scope: String)
GET /authorize/slack controllers.SlackAuthController.authorize
GET /authorize/slack controllers.SlackAuthController.authorize

GET /login/vkontakte controllers.VkontakteAuthController.login(scope: String)
GET /link/vkontakte controllers.VkontakteAuthController.link(scope: String)
GET /authorize/vkontakte controllers.VkontakteAuthController.authorize
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ trait OAuth2Authenticator extends OAuthAuthenticator {

def getAuthorizationUrl(scope: String, state: String): String

def parseAccessTokenResponse(response: WSResponse): String
def parseAccessTokenResponse(response: WSResponse): AccessToken

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class FacebookAuthenticator extends OAuth2Authenticator {
s"${authorizationUrl}?client_id=${encodedClientId}&redirect_uri=${encodedRedirectUri}&scope=${encodedScope}&state=${encodedState}"
}

def parseAccessTokenResponse(response: WSResponse): String = {
def parseAccessTokenResponse(response: WSResponse): AccessToken = {
Logger(getClass).debug("Parsing access token response: " + response.body)
try {
(for {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class GitHubAuthenticator extends OAuth2Authenticator {
s"${authorizationUrl}?client_id=${encodedClientId}&redirect_uri=${encodedRedirectUri}&scope=${encodedScope}&state=${encodedState}"
}

def parseAccessTokenResponse(response: WSResponse): String = {
def parseAccessTokenResponse(response: WSResponse): AccessToken = {
Logger(getClass).debug("Parsing access token response: " + response.body)
try {
(response.json \ "access_token").as[String]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package jp.t2v.lab.play2.auth.social.providers.vkontakte

import java.net.URLEncoder

import jp.t2v.lab.play2.auth.social.core.{AccessTokenRetrievalFailedException, OAuth2Authenticator}
import play.api.Logger
import play.api.Play.current
import play.api.http.{HeaderNames, MimeTypes}
import play.api.libs.json.Json
import play.api.libs.ws.{WS, WSResponse}
import play.api.mvc.Results

import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal


class VkontakteAuthenticator extends OAuth2Authenticator {

type AccessToken = VkontakteToken

val providerName: String = "vkontakte"

val accessTokenUrl = "https://oauth.vk.com/access_token"

val authorizationUrl = "https://oauth.vk.com/authorize"

val display = "page"

val response_type = "code"

lazy val clientId = current.configuration.getString("vkontakte.clientId").getOrElse(sys.error("vkontakte.clientId is missing"))

lazy val clientSecret = current.configuration.getString("vkontakte.clientSecret").getOrElse(sys.error("vkontakte.clientSecret is missing"))

lazy val callbackUrl = current.configuration.getString("vkontakte.callbackURL").getOrElse(sys.error("vkontakte.callbackURL is missing"))

override def retrieveAccessToken(code: String)(implicit ctx: ExecutionContext): Future[AccessToken] = {
WS.url(accessTokenUrl)
.withQueryString(
"client_id" -> clientId,
"client_secret" -> clientSecret,
"redirect_uri" -> callbackUrl,
"code" -> code)
.withHeaders(HeaderNames.ACCEPT -> MimeTypes.JSON)
.post(Results.EmptyContent())
.map { response =>
Logger(getClass).debug("Retrieving access token from provider API: " + response.body)
parseAccessTokenResponse(response)
}
}

override def getAuthorizationUrl(scope: String, state: String): String = {
val encodedClientId = URLEncoder.encode(clientId, "utf-8")
val encodedRedirectUri = URLEncoder.encode(callbackUrl, "utf-8")
val encodedScope = URLEncoder.encode(scope, "utf-8")
val encodedState = URLEncoder.encode(state, "utf-8")
s"${authorizationUrl}?client_id=${encodedClientId}" +
s"&redirect_uri=${encodedRedirectUri}" +
s"&display=${display}" +
s"&response_type=${response_type}" +
s"&scope=${encodedScope}" +
s"&state=${encodedState}"
}

override def parseAccessTokenResponse(response: WSResponse): VkontakteToken = {
Logger(getClass).debug("Parsing access token response: " + response.body)
try {
val jsonValue = Json.parse(response.body.toString)
var access_token = jsonValue \ "access_token"
var email = jsonValue \ "email"
var expires_in = jsonValue \ "expires_in"
var user_id = jsonValue \ "user_id"
new VkontakteToken(access_token.get.toString(), email.get.toString(), expires_in.get.as[Long], user_id.get.as[Long])
} catch {
case NonFatal(e) =>
throw new AccessTokenRetrievalFailedException(s"Failed to retrieve access token. ${response.body}", e)
}
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package jp.t2v.lab.play2.auth.social.providers.vkontakte

/**
* Created by Yuri Rastegaev on 19.03.2016.
*/

import jp.t2v.lab.play2.auth.social.core.OAuth2Controller
import jp.t2v.lab.play2.auth.{AuthConfig, Login, OptionalAuthElement}

trait VkontakteController extends OAuth2Controller
with AuthConfig
with OptionalAuthElement
with Login {

val authenticator = new VkontakteAuthenticator

}
Loading