Skip to content

Instantly share code, notes, and snippets.

@k4200
Created January 15, 2011 08:46
Show Gist options
  • Save k4200/780788 to your computer and use it in GitHub Desktop.
Save k4200/780788 to your computer and use it in GitHub Desktop.
A trait for Lift framework that enables users to authenticate with Twitter.
/**
* Copyright Kazuo KASHIMA 2011
* k4200 [at] kazu [dot] tv
* Licensed under the same license as Lift framework (Apache 2.0)
*/
package code.model
import net.liftweb._
import mapper._
import common._
import util._
import util.Helpers._
import http._
import sitemap._
import sitemap.Loc._
import proto.{ProtoUser => GenProtoUser}
import _root_.scala.xml.{ NodeSeq, Text }
trait TwitterProtoUser[T <: TwitterProtoUser[T]] extends LongKeyedMapper[T] with IdPK
with UserIdAsString {
self: T =>
/**
* Convert the id to a String
*/
def userIdAsString: String = id.is.toString
object twitterAccount extends MappedString(this, 20) {
override def dbColumnName = "twitter_account"
}
object accessToken extends MappedString(this, 100) { //50?
override def dbColumnName = "access_token"
override def dbDisplay_? = false
}
object accessTokenSecret extends MappedString(this, 100) { //43?
override def dbColumnName = "access_token_secret"
override def dbDisplay_? = false
}
/**
* The superuser field for the User. You can override the behavior
* of this field:
* <pre name="code" class="scala">
* override lazy val superUser = new MySuperUser(this) {
* println("I am doing something different")
* }
* </pre>
* Borrowed from ProtoUser.
*/
lazy val superUser: MappedBoolean[T] = new MySuperUser(this)
protected class MySuperUser(obj: T) extends MappedBoolean(obj) {
override def defaultValue = false
}
/**
* The unique id field for the User. This field
* is used for validation, lost passwords, etc.
* You can override the behavior
* of this field:
* <pre name="code" class="scala">
* override lazy val uniqueId = new MyUniqueId(this, 32) {
* println("I am doing something different")
* }
* </pre>
*/
lazy val uniqueId: MappedUniqueId[T] = new MyUniqueId(this, 32)
protected class MyUniqueId(obj: T, size: Int) extends MappedUniqueId(obj, size) {
override def dbIndexed_? = true
override def writePermission_? = true
}
/**
* The has the user been validated.
* You can override the behavior
* of this field:
* <pre name="code" class="scala">
* override lazy val validated = new MyValidated(this, 32) {
* println("I am doing something different")
* }
* </pre>
*/
lazy val validated: MappedBoolean[T] = new MyValidated(this)
protected class MyValidated(obj: T) extends MappedBoolean[T](obj) {
override def defaultValue = false
override val fieldId = Some(Text("txtValidated"))
}
}
/**
*
*/
trait MetaTwitterProtoUser[ModelType <: TwitterProtoUser[ModelType]]
extends LongKeyedMetaMapper[ModelType] with GenProtoUser {
self: ModelType =>
// override def dbTableName = "twitter_users"; // define the DB table name
// The code below is taken from
// net.liftweb.proto.ProtoUser and net.liftweb.mapper.MegaProtoUser
type TheUserType = ModelType
/**
* What's a field pointer for the underlying CRUDify
*/
type FieldPointerType = MappedField[_, TheUserType]
/**
* Based on a FieldPointer, build a FieldPointerBridge
*/
protected implicit def buildFieldBridge(from: FieldPointerType): FieldPointerBridge = new MyPointer(from)
protected class MyPointer(from: FieldPointerType) extends FieldPointerBridge {
/**
* What is the display name of this field?
*/
def displayHtml: NodeSeq = from.displayHtml
/**
* Does this represent a pointer to a Password field
*/
def isPasswordField_? : Boolean = from match {
case a: MappedPassword[_] => true
case _ => false
}
}
/**
* Convert an instance of TheUserType to the Bridge trait
*/
protected implicit def typeToBridge(in: TheUserType): UserBridge =
new MyUserBridge(in)
/**
* Bridges from TheUserType to methods used in this class
*/
protected class MyUserBridge(in: TheUserType) extends UserBridge {
/**
* Convert the user's primary key to a String
*/
def userIdAsString: String = in.id.toString
/**
* Return the user's first name
*/
def getFirstName: String = ""
/**
* Return the user's last name
*/
def getLastName: String = ""
/**
* Get the user's email
*/
def getEmail: String = ""
/**
* Is the user a superuser
*/
def superUser_? : Boolean = in.superUser
/**
* Has the user been validated?
*/
def validated_? : Boolean = in.validated
/**
* Does the supplied password match the actual password?
*/
def testPassword(toTest: Box[String]): Boolean = false
/**
* Set the validation flag on the user and return the user
*/
def setValidated(validation: Boolean): TheUserType =
in.validated(validation)
/**
* Set the unique ID for this user to a new value
*/
def resetUniqueId(): TheUserType = {
in.uniqueId.reset()
}
/**
* Return the unique ID for the user
*/
def getUniqueId(): String = in.uniqueId
/**
* Validate the user
*/
def validate: List[FieldError] = in.validate
/**
* Given a list of string, set the password
*/
def setPasswordFromListString(pwd: List[String]): TheUserType = {
//in.password.setList(pwd)
in
}
/**
* Save the user to backing store
*/
def save(): Boolean = in.save
}
/**
* Given a field pointer and an instance, get the field on that instance
*/
protected def computeFieldFromPointer(instance: TheUserType, pointer: FieldPointerType): Box[BaseField] = Full(getActualField(instance, pointer))
/**
* Given an twitter account, find the user
*/
protected def findUserByUserName(twitterAccountName: String): Box[TheUserType] =
find(By(twitterAccount, twitterAccountName))
/**
* Given a unique id, find the user
*/
protected def findUserByUniqueId(id: String): Box[TheUserType] =
find(By(uniqueId, id))
/**
* Create a new instance of the User
*/
protected def createNewUserInstance(): TheUserType = self.create
/**
* Given a String representing the User ID, find the user
*/
protected def userFromStringId(id: String): Box[TheUserType] = find(id)
/**
* The list of fields presented to the user at sign-up
*/
def signupFields: List[FieldPointerType] = List(twitterAccount)
/**
* The list of fields presented to the user for editing
*/
def editFields: List[FieldPointerType] = List(twitterAccount)
// def editFields: List[FieldPointerType] = List(firstName,
// lastName,
// email,
// locale,
// timezone)
// ----------------- menus
override val basePath: List[String] = "user_mgt" :: Nil
def callbackSuffix = "callback"
lazy val callbackPath = thePath(callbackSuffix)
// Only login functionality is needed.
// override def logoutMenuLoc: Box[Menu] = Empty
override def createUserMenuLoc: Box[Menu] = Empty
override def lostPasswordMenuLoc: Box[Menu] = Empty
override def resetPasswordMenuLoc: Box[Menu] = Empty
override def editUserMenuLoc: Box[Menu] = Empty
override def changePasswordMenuLoc: Box[Menu] = Empty
override def validateUserMenuLoc: Box[Menu] = Empty
def callbackMenuLoc: Box[Menu] =
Full(Menu(Loc("Callback", (callbackPath, true), S.??("callback"), callbackMenuLocParams)))
override lazy val sitemap: List[Menu] =
List(loginMenuLoc, logoutMenuLoc, createUserMenuLoc,
lostPasswordMenuLoc, resetPasswordMenuLoc,
editUserMenuLoc, changePasswordMenuLoc,
validateUserMenuLoc, callbackMenuLoc).flatten(a => a)
protected def callbackMenuLocParams: List[LocParam[Unit]] =
Hidden ::
//snarfLastItem is defined in ProtoUser.
// Template(() => wrapIt(callback(snarfLastItem))) ::
Template(() => callback(S.request)) ::
// If(notLoggedIn_? _, S.??("logout.first")) ::
Nil
// override lazy val ItemList: List[MenuItem] =
// List(MenuItem(S.??("log.in"), loginPath, false))
override lazy val ItemList: List[MenuItem] =
List(MenuItem(S.??("sign.up"), signUpPath, false),
MenuItem(S.??("log.in"), loginPath, false),
MenuItem(S.??("lost.password"), lostPasswordPath, false),
MenuItem("", passwordResetPath, false),
MenuItem(S.??("change.password"), changePasswordPath, true),
MenuItem(S.??("log.out"), logoutPath, true),
MenuItem(S.??("edit.profile"), editPath, true),
MenuItem("", validateUserPath, false),
MenuItem("", callbackPath, false))
// ----------------- Methods related to authentication
/**
* How do we prompt the user for the username. By default,
* it's S.??("email.address"), you can can change it to something else
* TODO Add a text to the property files.
*/
override def userNameFieldString: String = S.??("twitter.account")
override def loginXhtml = {
(<form method="post" action={S.uri}><table><tr><td
colspan="2">{S.??("log.in")}</td></tr>
<tr><td colspan="2"><user:submit /></td></tr></table>
</form>)
}
import code.lib.TwitterOAuth
override def login = {
if (S.post_?) {
val url = TwitterOAuth.authenticateUrl
S.redirectTo(url.toString())
}
bind("user", loginXhtml,
"email" -> (<input type="text" name="username"/>),
"submit" -> (<input type="submit" value={S.??("log.in")}/>))
}
import dispatch.oauth._
/**
*
* @param request
* @return
*/
def callback(request: Box[Req]):NodeSeq = {
//
(for (oauthToken <- S.param("oauth_token");
oauthVerifier <- S.param("oauth_verifier"))
yield {
// access tokenの取得
val (accessToken: Token, userId: String, screenName: String)
= TwitterOAuth.getAccessToken(oauthToken, oauthVerifier)
// Access tokenとAccess secretはユーザーのDBとかに保存しとけばOK
val user = saveAccessToken(screenName, accessToken)
logUserIn(user, () => {
S.redirectTo(homePage)
})
}) getOrElse {
S.error("invalid token")
S.redirectTo(homePage)
}
}
private def saveAccessToken(screenName: String, accessToken: Token): TheUserType = {
val user = findUserByUserName(screenName).getOrElse({
val user = createNewUserInstance
user.twitterAccount.set(screenName)
user
})
user.accessToken.set(accessToken.value)
user.accessTokenSecret.set(accessToken.secret)
user.save
user
}
}
@k4200
Copy link
Author

k4200 commented Jan 15, 2011

TwitterOAuth is here (an old version and requires small changes though):
http://bazaar.launchpad.net/~k4200/+junk/liftchat/view/head:/src/main/scala/code/lib/TwitterOAuth.scala

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment