Consider the following example: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html#the-listener
I want to see if the new authenticated token is the same as for the currently logged in user. See below.
try {
$currentToken = $this->securityContext->getToken();
$authToken = $this->authenticationManager->authenticate($token);
// What is the best way to compare these two? Both implement TokenInterface.
if ([compare tokens]) {
// already logged in
return;
}
$this->tokenStorage->setToken($authToken);
return;
} catch (AuthenticationException $failed) {
}
I considered comparing UserInterface->getUsername():
if ($currentToken->getUser()->getUsername() === $authToken->getUser()->getUsername()) {
But wonder if there is a better solution...
I assume both user object aren't the same reference. Because if they are you could compare them using === to check if they are the same.
Given that username is unique than this is one way to check if it is the same user.
But in some systems the username doesn't have to be unique. So comparing the id's might be better since they should alway be unique.
What about leveraging the code they show in the authenticate() function?
$currentUser = $this->userProvider->loadUserByUsername($currentToken->getUsername());
then
$tokenUser = $this->userProvider->loadUserByUsername($authToken->getUsername());
then
if ($currentUser == $tokenUser) {
Related
I follow this instruction to create a #userAware annotation which automatically limits all queries on specific entities to the current user.
For example $todoRepo->findByTag("someTag") will automatically add WHERE user_id = 123 to the resulting query to make sure that only ToDo entities of the current user (id 123) can be accessed.
This works great but has one big downside: If for example the admin whats to know how many ToDo entities use the tag someTag he will only find his own entities...
Since using an #userAware annotation seems to be quite common, I wonder if there is any best practice on how to disable/bypass/override this filter/annotation on a specific query.
You can disable the filter. In your example, you must change the kernel request event, like that ...
public function onKernelRequest()
{
if ($user = $this->getUser()) {
if ($user->isAdmin() /* or whatever */) {
$this->em->getFilters()->disable('user_filter');
} else {
$filter = $this->em->getFilters()->enable('user_filter');
$filter->setParameter('id', $user->getId());
$filter->setAnnotationReader($this->reader);
}
}
}
For example,
findByType: function(type) {
check(type, String);
var role;
if(type === 'user') {
role = 'user'
} else if(type === 'admin') {
role = 'admin'
}
Accounts.find({role: role})
}
in this case, check(type, String); is not necessary?
so if I have my own check codes, there is no need to use audit-argument-checks?
audit-argument-checks requires all your Meteor methods to actually check each of their arguments, if they don't, an error message will be logged on the server and presented to the client as a "500 Internal server error".
This package helps you making sure all your methods implement basic security checking, but it does not check your arguments for you (it can't guess the intended types of your arguments), so you still need to call check unless you want this package to throw errors.
https://docs.meteor.com/#/full/auditargumentchecks
You could also improve your check by doing the following (unless you want to be searching for Accounts.find({role: null}) when the type is neither 'admin' nor 'user'):
findByType: function(type) {
check(type, Match.Where((type)=>{
check(type, String);
return type=="user" || type=="admin";
}));
Accounts.find({role: type});
}
I know it's been a while this question has been asked but I found it while searching for something else and thought I could suggest a better form.
I am using the meteor package ian:accounts-ui-bootstrap-3 for accounts and alanning:roles for assigning roles.
On the sign up form I have two options one for Doctor and one for Advisor. I want to assign the selected option as a role to that user. Can someone let me know how to do this?
I have just started learning meteor and don't know much about its flow. I can assign roles to a user if I create the user manually like this:
var adminUser = Meteor.users.findOne({roles:{$in:["admin"]}});
if(!adminUser){
adminUser = Accounts.createUser({
email: "mohsin.rafi#mail.com",
password: "admin",
profile: { name: "admin" }
});
Roles.addUsersToRoles(adminUser, [ROLES.Admin]);
}
But I want to assign a roll automatically as a user signs up and select one of the options and that option should be assigned as his role.
You shouldn't need a hack for this. Instead of using Accounts.onCreateUser you can do it with the following hook on the Meteor.users collection. Something along the lines of the following should work:
Meteor.users.after.insert(function (userId, doc) {
if (doc.profile.type === "doctor") {
Roles.addUsersToRoles(doc._id, [ROLES.Doctor])
} else if (doc.profile.type === "advisor") {
Roles.addUsersToRoles(doc._id, [ROLES.Advisor])
}
});
To get around having to check on login every time it's possible to directly set the roles on the user object instead of using the Roles API.
A hack? Yep, you probably need to make sure the roles have already been added to roles... not sure if there's anything else yet.
if(Meteor.isServer){
Accounts.onCreateUser(function(options, user){
if(options.roles){
_.set(user, 'roles.__global_roles__', ['coach', options.roles]);
}
return user;
});
}
Note: _.set is a lodash method not in underscorejs.
There's no pretty solution because:
There's no server side meteor callback post complete account creation.
In onCreateUser the user hasn't been added to the collection.
Accounts.createUser's callback is currently only for use on the client. A method could then be used from that callback but it would be insecure to rely on it.
The roles package seems to grab the user from the collection and in onCreateUser it's not there yet.
you can use the Accounts.onCreateUser hook to manage that.
Please keep in mind the code below is fairly insecure and you would probably want to do more checking beforehand, otherwise anyone can assign themselves admin. (from docs):
options may come from an untrusted client so make sure to validate any values you read from it.
Accounts.onCreateUser(function (options, user) {
user.profile = options.profile || {};
if (_.has(options, 'role')) {
Roles.addUserToRoles(user._id, options.role);
}
return user;
});
Thanks for your response. I tried but it doesn't work for me.
I used Accounts.onLogin hook to to manage this. Below code works for me:
Accounts.onLogin(function (info) {
var user = info.user;
if(user.profile.type === "doctor"){
Roles.addUsersToRoles(user, [ROLES.Doctor])
}
else
if(user.profile.type === "advisor"){
Roles.addUsersToRoles(user, [ROLES.Advisor])
}
return user;
});
I have these requirements:
User will log in via email and multiple Oauth providers. I use function like User.findByEmail('1#1.com'). So I need to have permision to see the list of users before being authenticated.
User's email address, geolocation and age should be kept secretly from other users.
My first plan was:
users:{
$user-id:{
// public
".read" : true,
name,
created_at,
private: {
".read" : "auth.email === data.child('email').val()",
age,
email,
geolocation,
}
$piority = email
}
}
Then I realized no, it just doesn't work that way. So would anyone please tell me how to do it correctly? Thanks in advance.
P.S. Firebase really needs a filter/serialize method.
There are really several questions in this post. The first is how to store private data. One simple change you can make is to invert the public/private and user keys--they don't have to be nested in the same user record.
/users/$user_id/...public data...
/private/$user_id/...private data...
This makes securing the data quite a bit simpler.
Another question is regarding duplicate email detection. If we assume you are using simple login here, this is all pretty moot. You can check to see if an email address exists by simply trying to create the account. An error will be returned if the email is already registered.
If that doesn't solve it, you can still check this manually, without providing a list of users' emails. This is typically done with an index. When a new account is created, write something like this:
/email_index/$escaped_email/$userid ($userid is the value)
Now when you want to check if the email is available, you do a read like this:
var ref = new Firebase(URL);
function checkEmail(emailAddress, callback) {
ref.child('email_index/'+escapeEmail(emailAddress)).once('value', function(snap) {
callback(snap.val() !== null);
});
}
function escapeEmail(emailAddress) {
return (email || '').replace('.', ',');
}
checkEmail(EMAIL_ADDRESS, function(exists) {
console.log(EMAIL_ADDRESS + (exists? 'does' : 'does not') + ' exist!');
});
To prevent someone from listing the emails, you do something like this in your (amazingly flexible and really quite sophisticated, even for enterprise apps) security rules:
"email_index": {
// no .read rule here means that the data cannot be listed; I have to know the email address to check it
"$email_address": {
".read": true,
// it can only be claimed once and the value must be my user id
".write": "auth.uid === newData.val() && !data.exists()"
}
}
hope my first question here is not a stupid one.
Lets say we want to build a chat application with meteor, with logedin and anonymous users. The chat should be filled like that:
var post = {
userId: user._id, // is empty if anonymous user
submitted: new Date().getTime(),
text: chat_message
});
var postId = Posts.insert(post);
The publication could looks like this to make sure that the userId is not transfered
Meteor.publish('getTheChat', function() {
return Post.find({}, {fields: {userId: false});
});
But is there a way to add a field in the returned collection dynamically?
The userId should not be published but a status like "Your_Post","another_logedin_user" or "an_anonymous_user". By having that, I could include some css, so the chat looks a little bit more like WhatsApp or iMessage.
The logic inside the publish method could be something like
if (userId == this.userId) {
status = "Your_Post";
} else if (userId != null) {
status = "another_logedin_user";
} else {
status = "an_anonymous_user";
}
You see, the publication should include different values when called from different users. Is there a way in Meteor.publish?
Thanks for any insight or suggestions
Thank you both for your ideas! But as I had to find out (just for my inward peace) how it is possible inside the publish method server sided, I came, with the help of David's link, to this solution -maybe it will help someone later:
Meteor.publish('getTheChat', function(postId) {
var currentUserId = this.userId;
var ownerUserId = Posts.findOne({'_id':postId}).userId;
var findOptions = {}; // in my final coding these differ for 'chat room owners' and 'normal users'
var transform = function(post) {
if (currentUserId && post.userId == currentUserId) {
post.userId = "posted by you";
} else if (post.userId == null) {
post.userId = "anonym posted";
} else if (post.userId == ownerUserId) {
post.userId = "posted by owner";
} else {
post.userID = "posted by another loged in";
return post;
};
var self = this;
var handle = Posts.find(findOptions).observe({
added: function (document) {
self.added('posts', document._id, transform(document));
},
changed: function (newDocument, oldDocument) {
self.changed('posts', document._id, transform(newDocument));
},
removed: function (oldDocument) {
self.removed('posts', oldDocument._id);
}
});
self.ready();
self.onStop(function(){
handle.stop();
});
By having this I am finally able to overwrite values dynamically.
It looks like you need to add a transform on your Posts collection. You can do this in your publish function (as seen here), but server-side transforms are computationally inefficient and tricky to write. Though they are necessary in cases where only the server could perform the action - e.g. signed URLs. In your case, I'd recommend a standard collection transform which is a filter applied after the documents are fetched.
Unfortunately, this kind of transform would require the userId on the client. I've never seen a case where simply publishing a id could cause a security issue. If you believe this is the case with your app, I'm very interested to know why. If you can overcome this restriction, keep reading...
You can read about transforms in the documentation on collections, and you can see an example on evented mind. Personally I like to use the collection-helpers package for this.
If you try collection-helpers, your transform could look like:
Posts.helpers({
status: function() {
if (this.userId === Meteor.userId()) {
return 'Your_Post';
} else if (this.userId != null) {
return 'another_logedin_user';
} else {
return 'an_anonymous_user';
}
}
});
And then you could use it in your template like:
{{#each posts}}
<p>{{text}} - <span class='status'>{{status}}</span></p>
{{/each}}
Of course, you can also use template helpers to achieve the same result but transforms are more easily reusable across your application.
Sadly, this has been a huge issue for me too, and I am sorry to say, it is not technically possible to just add a field on the publisher's query and use it conveniently in your view. BUT, I have a solution that may work for you. It will also give you an idea of how complex it can become as soon as you want to keep some reactive data private in Meteor.
Server side:
First, create two different publishers: one for the current user's posts, one for all the others:
Meteor.publish('getTheOthersChat', function() {
return Post.find({$ne:{userId: this.userId}}, {fields: {userId: false});
});
Meteor.publish('getTheOwnChat', function() {
return Post.find({userId: this.userId});
});
Client/router side:
Then, subscribe to both of these: what this will do is include the post's userId only when it is the own user's id. If not, it'll be undefined.
Then, we still need to identify the case "anonymously posted" vs "posted by user". For this, you can add another field during the post creation, for example is_anonymous, which you then set to true or false depending on the case if the user is logged in or not. The check would then be:
if (userId) {
status = "Your_Post";
} else if (is_anonymous === false) {
status = "another_logedin_user";
} else {
status = "an_anonymous_user";
}
This solution should work. I know, it is sad to have to come to this kind of means. It makes Meteor look clunky and impractical for tasks that should be dead easy. Such a shame for such a cool framework!