Using Play! 2.0.4 and SecureSocial 2 (http://securesocial.ws/). Scala implementation. Most of this question will be directly referencing the sample here: https://github.com/jaliss/securesocial/blob/2.0.12/samples/scala/demo/app/service/InMemoryUserService.scala
I'm trying to figure out the author's original intention as to backend interaction with a storage service. With respect to the def find(id: UserId) and the def findByEmailAndProvider(email: String, providerId: String): methods, is SecureSocial expecting to give either a Facebook ID or email that can be used to return a full SocialUser class?
If that's the case, then how do we assign our own IDs to each user so that we can link accounts together? Because it seems that if I extend Identity to include a universal ID then would that also require rewriting/extending the social providers, too?
At minimum, I'm trying to figure out what API/parameters I should expose for the find and save methods in a backend service. Let me know if this question needs to be clarified :)
After having a couple days to make some design considerations and better understand SecureSocial, I realized that implementing the find and save methods were not that difficult to understand. It's properly designing the logic in a backend service that matters.
Basically, I created a PlatformUser class that extends the Identity class and includes User ID and profile data pulled from a backend class. Here's how it looks:
case class PlatformUser(
guid: String,
suspended: Boolean,
id: UserId,
firstName: String,
lastName: String,
fullName: String,
email: Option[String],
avatarUrl: Option[String],
authMethod: AuthenticationMethod,
oAuth1Info: Option[OAuth1Info] = None,
oAuth2Info: Option[OAuth2Info] = None,
passwordInfo: Option[PasswordInfo] = None,
communityProfile: Option[String] = None
) extends Identity
My object PlatformUser contains code that accesses a backend HTTP API to transfer data back and forth. Here's how I implement the find and save methods:
def find(id: UserId): Option[PlatformUser] = {
PlatformUser.fetch(id)
}
def findByEmailAndProvider(email: String, providerId: String): Option[PlatformUser] = {
PlatformUser.fetch(email, providerId)
}
def save(user: Identity): PlatformUser = {
PlatformUser.store(user)
}
The logic for merging accounts remains in the backend service as well. Now if the user doesn't already exist, the backend service generates a platform ID. If an email of an incoming Identity is found to already exist on the platform, then an auto-link of identities is performed to the existing platform ID (unless its found that the email is being used on multiple accounts for the same social network, where an error will be triggered). The user is notified by email to their primary address of the auto-link.
The last thing left is populating the communityProfile. If the backend service doesn't find one, then that field returns as None. I then automatically redirect the user to a "registration" page where they need to complete their profile.
That's about it. I hope this helps future devs who are trying to figure out more complicated uses of SecureSocial.
"If an email of an incoming Identity is found to already exist on the platform, then an auto-link of identities is performed to the existing platform ID". I am assuming when you say auto-link, this would be during sign on. If so, this would be a security flaw.
A malicious user could set his twitter email to your mail id. When he logs in using twitter, it gets "auto-linked" to your account!
See this thread for further analysis https://github.com/jaliss/securesocial/issues/14
Related
so I already finished all of the actual app for this. I just need to setup the backend. I figured Firebase was the best solution since Parse is no longer a thing. What I wanted was:
Users with profiles - These profiles can be viewed by added friends but only edited (written) to by the actual profile owner.
So I read through the Firebase Docs and still cannot really figure out how to do this. They only have 1 Swift application example that does not do anything similar and the one Obj C twitter one, will not even build. All of their docs still have println for Swift which just makes me think it is not updated frequently.
Does anyone have any good examples / tutorials of this? I keep trying to search for things but nothing is as similar enough to what I want. I am more looking on how to setup the db for each user and access it rather actually using Firebase in Swift.
As I wrote in my comment to your question, this answer is based on what we do in a real social app Impether using Swift + Firebase.
Data structure
Let's assume that you want to store the following information for a single user:
email
username
name
followers - number of people who follow a particular user
following - number of people who a particular user follows
avatar_url - url of their avatar
bio - some additional text
Since in Firebase everything is stored a JSON objects, you can store the above structure under node with path like users/$userId, where $userId is Firebase User UID which is created for each registered user if you use simple email/password Firebase authorization.
Firebase email/password authorization is described in their docs:
https://www.firebase.com/docs/ios/guide/user-auth.html
https://www.firebase.com/docs/ios/guide/login/password.html
Notice that there are both Obj-C and Swift snippets. I find Firebase documentation really great as it helped me a lot when I was building our app.
For the purpose of this answer let's assume that we have user with username jack and Firebase User UID equal to jack_uid (in reality this will be a string generated by Firebase).
Then an example data for this user will be store under a path users/jack_uid and can look like this:
{
"email" : "jack#example.com",
"username" : "jack",
"name" : "Jack",
"followers" : 8,
"following" : 11,
"avatar_url" : "http://yourstoragesystem.com/avatars/jack.jpg",
"bio" : "Blogger, YouTuber",
}
Firebase email/password authorization works really well, but let's be honest, if user wants to sign in into the app, it's a lot better for him to use his username than his email he gave while he registering his account.
In order to do that, we decided to store a mapping from usernames to user ids. The idea is that if user inputs his username and password in a login form, we use that mapping to retrieve his user id and then we try to sign him in using his user id and provided password.
The mapping can be stored for example under a path username_to_uid and looks like this:
{
"sample_username_1": "firebase_generated_userid_1",
"sample_username_2": "firebase_generated_userid_2",
...
"jack": "jack_uid",
"sample_username_123": "firebase_generated_userid_123"
}
Then creating a profile may looks like this and it's done as soon as registration of a new account was successful (this snippet is very close to the exact code we use in the production):
func createProfile(uid: String, email: String,
username: String, avatarUrl: String,
successBlock: () -> Void, errorBlock: () -> Void) {
//path to user data node
let userDataPath = "/users/\(uid)"
//path to user's username to uid mapping
let usernameToUidDataPath = "/username_to_uid/\(username)"
//you want to have JSON object representing user data
//and we do use our User Swift structures to do that
//but you can just create a raw JSON object here.
//name, avatarUrl, bio, followers and following are
//initialized with default values
let user = User(uid: uid, username: username, name: "",
avatarUrl: avatarUrl, bio: "",
followers: 0, following: 0)
//this produces a JSON object from User instance
var userData = user.serialize()
//we add email to JSON data, because we don't store
//it directly in our objects
userData["email"] = email
//we use fanoutObject to update both user data
//and username to uid mapping at the same time
//this is very convinient, because either both
//write are successful or in case of any error,
//nothing is written, so you avoid inconsistencies
//in you database. You can read more about that technique
//here: https://www.firebase.com/blog/2015-10-07-how-to-keep-your-data-consistent.html
var fanoutObject = [String:AnyObject]()
fanoutObject[userDataPath] = userData
fanoutObject[usernameToUidDataPath] = uid
let ref = Firebase(url: "https://YOUR-FIREBASE-URL.firebaseio.com/images")
ref.updateChildValues(fanoutObject, withCompletionBlock: {
err, snap in
if err == nil {
//call success call back if there were no errors
successBlock()
} else {
//handle error here
errorBlock()
}
})
}
In addition to this you possibly want to store for each user a list of his followers and a separate list of users he follows. This can be done just by storing user ids at a path like followers/jack_uid, for example it can look like this:
{
"firebase_generated_userid_4": true,
"firebase_generated_userid_14": true
}
This is the way we store sets of values in our app. It very convenient, because it is really user to update it and check if some value is there.
In order to count the number of followers, we put this counter into user's data directly. This makes reading the counter very efficient. However, updating this counter requires using transactional writes and the idea is almost exactly the same as in my answer here: Upvote/Downvote system within Swift via Firebase
Read/write permissions
A part of your question is how to handle permissions to data you store. The good news is that Firebase is exceptionally good here. If you go to your Firebase dashboard there is a tab named Security&Rules and this is the place where you control permissions to your data.
What's great about Firebase rules is that they are declarative, which makes them very easy to use and maintain. However, writing rules in pure JSON is not the best idea since it's quite hard to control them when you want to combine some atomic rules into a bigger rule or your app simple grows and there are more and more different data you store in your Firebase database. Fortunately, Firebase team wrote Bolt, which is a language in which you can write all rules you need very easily.
First of all I recommend to read Firebase docs about Security, especially how does permission to a node influences permission for its children. Then, you can take a look at Bolt here:
https://www.firebase.com/docs/security/bolt/guide.html
https://www.firebase.com/blog/2015-11-09-introducing-the-bolt-compiler.html
https://github.com/firebase/bolt/blob/master/docs/guide.md
For example, we use rules for managing users data similar to this:
//global helpers
isCurrentUser(userId) {
auth != null && auth.uid == userId;
}
isLogged() {
auth != null;
}
//custom types, you can extend them
//if you want to
type UserId extends String;
type Username extends String;
type AvatarUrl extends String;
type Email extends String;
type User {
avatar_url: AvatarUrl,
bio: String,
email: Email,
followers: Number,
following: Number,
name: String,
username: Username,
}
//user data rules
path /users/{$userId} is User {
write() { isCurrentUser($userId) }
read() { isLogged() }
}
//user's followers rules
//rules for users a particular
//user follows are similar
path /followers/{$userId} {
read() { isLogged() }
}
path /followers/{$userId}/{$followerId} is Boolean {
create() { isCurrentUser($followerId) && this == true }
delete() { isCurrentUser($followerId) }
}
//username to uid rules
path /username_to_uid {
read() { true }
}
path /username_to_uid/{$username} is UserId {
create() { isCurrentUser(this) }
}
The bottom line is that you write rules you want using Bolt, then you compile them into JSON using Bolt compiler and then you deploy them into your Firebase, using command line tools or by pasting them into dashboard, but command line is way more efficient. A nice additional feature is that you can test your rules by using tools in Simulator tab in your dashboard.
Summary
For me Firebase is a great tool for implementing a system you want. However, I recommend to start with simple features and learn how to use Firebase in the first place. Implementing social app with functionality like for example Instagram is quite a big challenge, especially if you want to do it right :) It's very tempting to put all functionality there very quickly and Firebase makes it relatively easy to do, but I recommend to be patient here.
In addition, take your time and invest in writing tools. For example, we have two separated Firebase databases, one for production and second for testing, which is really important if you want to write unit and UI tests efficiently.
Also, I recommend building permission rules from the beginning. Adding them later may be tempting, but also quite overwhelming.
Last but not least, follow Firebase blog. They post regularly and you can be up to date with their latest features and updates - this is how I learnt how to use concurrent writes using fanout technique.
I've user roles: user, manager, admin. I need to authenticate them in controllers (methods). For example only admin can delete (now it looks like this, need to change that only admin should have permission):
def deleteBook(id: Int) = DBAction {
findById(id) match {
case Some(entity) => {
books.filter(_.id === id).delete
Ok("")
}
case None => Ok("")
}
}
I've many controllers and methods. I need to authenticate before process request (for example deleting book). My routes file contains:
...
DELETE /books/:id #controllers.Book.deleteBook(id: Int)
...
Some routes are only accessible to admin and manager. Some are for all types of users.
I'm currently seeing deadbolt2scala authorization module for play.
Can you recommend best way to authenticate multirole users in playframework scala?
I've managed to do this by using StackableControllers provided by https://github.com/t2v/stackable-controller
Basically, I use a basic access control list provided by my application.conf. I start by checking if there is a user in my request. If there is one, I can check if he has sufficient access rights to perform the action.
Such a feature may be implemented using BodyParser composition too. I've never done that, though, so someone else's advice may be better for you.
I have a list of User objects, and each of those User objects looks like this:
case class User(username: String, firstName: String, lastName: String, batchId: Int, imageUrl: String)
I want to go through that List, pull out all the usernames, and send those usernames off to an API which will return a JSON list containing Twitter specific information (e.g. profile info and twitter profile image). I then want to take that list of returned objects and add the information in each of those to my original list of objects, matching by the username.
How can I do this in a functional way?
You do it pretty much as you said. I'll assume that you can figure out how to get in touch with the API and get JSON back, and the "how do I make it functional" part is the core of the question.
If you'll be querying in a batch, and you might not get back a username you requested, you can do something like the following.
val usernames = allUsers.map(_.username)
val json = myGetJSONRoutine(usernames)
val parsed = makeMapFromJSON(json) // Returns Map[String, TwitterInfo]
val newUsers = allUsers.map{ x =>
parsed.get(x.username).map{ t =>
// Generate the updated user object here
x.copy(imageUrl = t.imageUrl)
}.getOrElse(x) // Fall back to pre-existing object
}
Anyway, the basic steps are: map out the usernames, get the JSON, parse it into a map from username to whatever new info you need, and then map through the user records updating them with new information. Then you stop using allUsers and start using newUsers.
That's really the whole trick: instead of updating existing records, you regenerate the list with new records based on the old ones (copy is built for this kind of updating).
If your user record needs to be different after you get twitter info (that is, the original objects do not just contain stubs for the data), then you also need to write a default mapper from the un-twitterified User to the UserWithTwitter class. Or your User could have a twittery field that is an Option[TwitterInfo], which you start off setting to None and then copy(twittery = Some(t)) if you actually found that info.
A full tutorial on using a web API to get JSON, and then to parse JSON, is outside the scope of one question. (But e.g. Play can do it.)
I've created a grails app that uses spring security to allow a user to authenticate via facebook, and I can successfully print out the facebook username onto one of the views, so thus far I don't have any issues.
My problem lies when trying to bootstrap my application with some sample data for my given facebook user, so I don't have to enter it every time the application starts up.
This is how I'm trying to bootstrap my own facebook account, I have the following in Bootstrap.groovy :
def adminRole = new AppRole(authority: 'ROLE_ADMIN').save(flush: true)
def userRole = new AppRole(authority: 'ROLE_USER').save(flush: true)
def testUser = new AppUser(username: 'facebook_563645402', enabled: true,
password: 'my-hashed-pw-here',
surveys: [jamies])
testUser.save(flush: true)
AppUserAppRole.create testUser, adminRole, true
For the record, I've added a hasMany for the surveys field mentioned above onto AppUser.
When I fire up the app and try to connect, I get the following error :
URI
/web/j_spring_security_facebook_check
Class
grails.validation.ValidationException
Message
Validation Error(s) occurred during save(): - Field error in object 'web.AppUser' on field 'username': rejected value [facebook_563645402]; codes [web.AppUser.username.unique.error.web.AppUser.username,web.AppUser.username.unique.error.username,web.AppUser.username.unique.error.java.lang.String,web.AppUser.username.unique.error,appUser.username.unique.error.web.AppUser.username,appUser.username.unique.error.username,appUser.username.unique.error.java.lang.String,appUser.username.unique.error,web.AppUser.username.unique.web.AppUser.username,web.AppUser.username.unique.username,web.AppUser.username.unique.java.lang.String,web.AppUser.username.unique,appUser.username.unique.web.AppUser.username,appUser.username.unique.username,appUser.username.unique.java.lang.String,appUser.username.unique,unique.web.AppUser.username,unique.username,unique.java.lang.String,unique]; arguments [username,class web.AppUser,facebook_563645402]; default message [Property [{0}] of class [{1}] with value [{2}] must be unique]
Which appears to complain about the username not being unique.
If by trying to bootstrap some data breaks the unique constraints on the facebook username, how can I possibly ever pre define any data for a user?
A quick Googling brings up a few suggestions (link1, Grails spring security bootstrap, but so far they haven't helped, any ideas?
EDIT:
Delving deeper into the error that grails reports, I can see that the root of the above error is located in DefaultFacebookAuthDao, line 135, which mentions the following :
AppUserDomainClazz.withTransaction {
appUser.save(flush: true, failOnError: true)
}
So, by authenticating, spring security attempts to save a user domain object...
EDIT 2 :
This is my Bootstrap.groovy
def testUser = new AppUser(username: 'facebook_563645402', enabled: true,
password: 'my-hashed-pw', surveys: [new Survey(1)])
testUser.save()
def fbUser = new FacebookUser(uid: 563645402)
fbUser.save(flush: true)
Both FacebookUser and AppUser were generated via the spring security facebook quickstart, with the only change being to add static hasMany = [surveys: Survey] to AppUser.
It looks like the data has already been predefined, otherwise there wouldn't be a unique constraint violation. Just check for the existence of the data and only create it if needed:
def adminRole = AppRole.findOrSaveByAuthority('ROLE_ADMIN')
def userRole = AppRole.findOrSaveByAuthority('ROLE_USER')
String username = 'facebook_563645402'
if (!AppUser.findByUsername(username)) {
def testUser = new AppUser(username: username, enabled: true,
password: 'my-hashed-pw-here')
testUser.addToSurveys(jamies)
testUser.save()
AppUserAppRole.create testUser, adminRole, true
}
Spring Security Facebook tries to create a new user with same username (facebook_563645402). Because it cannot find it by uid.
As I see from you code, you just create an user, with filled username, but it's not a facebook user, and uid field isn't filled. So, the plugin cannot find any facebook user and tries to create a new one, using 'facebook_563645402' username by default.
There two ways: or change username for user created in Bootstrap (if it's not a facebook user), or create a Facebook User also (will be used for authentication).
A very basic question. I want to provide URIs for some objects in my application. For example, a resource is available at:
http://localhost:8080/myapp/user/1
However, I'd like to serialize such a User object. This serialization should contain the public URI of the object itself. So for example, the model has a new method serializeToSomething, which would serialize:
id: 1
username: JohnDoe
email: johndoe#example.com
publicURI: http://localhost:8080/myapp/user/1
How can I let the model instance know of its URL?
Some notes:
This has to happen within the scope of the model, controller or service, and not within the view. Also I don't want to hardcode this.
See related question Can I use grails tag outside of GSP?
Basically you can use g.createLink in a controller or service, and it will return a string. So you can do something like:
def uri = g.createLink(controller: 'user', action: 'show', id: user.id, absolute: true)
Personally I ended up serializing 3 things: controller name, action name (typically "show") and id. Then I used these three in g.createLink when displaying deserialized objects in a View.
Sure, this won't work if you need deserialized object for external usage.
You can get the url in a controller using
request.forwardURI
A similar question has been asked before