I have two different rules for my Firebase Realtime database.
One enables only authenticated users to read and write, while the other one is more complex. It only enables authenticated users to read and write data from the "rooms" node, and it enables users to work only with their own data in the "users" node.
However, when I use the second rule, I get the following error for some reasons I don't know.
System.NullReferenceException: Object reference not set to an instance
of an object
My two rules are:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
{
"rules": {
"users": {
"$uid": {
".write": "$uid === auth.uid",
".read": "$uid === auth.uid"
}
},
"rooms": {
".read": "auth != null",
".write": "auth != null"
}
}
}
My code which contains the error-causing line (3rd line):
void HandleValueChanged(object sender, ValueChangedEventArgs args) {
if(auth.CurrentUser != null) {
currentRoom = args.Snapshot.Child(auth.CurrentUser.UserId).Child("currentRoom").Value.ToString(); //this line causes the error
FirebaseDatabase.DefaultInstance.GetReference("rooms").Child(currentRoom).Child("members").Child(auth.CurrentUser.UserId).Child("type").GetValueAsync().ContinueWith(task => {
if (task.IsFaulted) {
// Handle the error...
} else if (task.IsCompleted) {
DataSnapshot snapshot = task.Result;
charType = snapshot.Value.ToString();
}
});
}
}
I tried to do some things in the rules playground in Firebase, but I didn't manage to get a solution there.
The HandleValueChanged method is working with the users node:
FirebaseDatabase.DefaultInstance.GetReference("users").ValueChanged += HandleValueChanged;
Your code tries to read the entire users node:
FirebaseDatabase.DefaultInstance.GetReference("users").ValueChange
But your security rules only allow a user read access to their own node:
{
"rules": {
"users": {
"$uid": {
".write": "$uid === auth.uid",
".read": "$uid === auth.uid"
}
},
Since the user doesn't have access to all of /users, reading from that node is rejected.
The problem is that rules are not filters, so they don't filter data on their own. Instead they merely ensure that the operations that the user performs don't conflict with the rules you've set for them. And since you didn't grant anyone read access to (all of) /users, the read is rejected.
In your case I'd highly recommend simply changing the code to only access the note that the user does have access to:
String uid = Firebase.Auth.FirebaseAuth.DefaultInstance.CurrentUser.UserId;
FirebaseDatabase.DefaultInstance.GetReference("users").Child(uid).ValueChanged = ...
In some other cases you might be able to combine a query and security rules to accomplish your needs. The query only requests the data that the user has access to, and the security rules ensure that the query matches those rules. For more on this, see the documentation on query based rules.
Related
Good evening, everybody. Trying to implement Api Versioning for my project. Faced with the problem that receiving default errors template response for request. Look code 1
{
"error": {
"code": "UnsupportedApiVersion",
"message": "The HTTP resource that matches the request URI 'https://localhost:5003/v2.3' does not support the API version '2.3'.",
"innerError": null
}
}
But I have my own error response template that want to receive.
{
"traceId": "0HMF1NONVN8SF:00000002",
"errors": [
{
"message": "",
"source": ""
}
]
}
As I understand I could implement my own ErrorResponseProvider, but could I avoid doing that?
How could I can disable ErrorResponse for ApiVersionOptions?
My api version configuration:
services
.AddApiVersioning(opt =>
{
opt.ApiVersionReader = new UrlSegmentApiVersionReader();
opt.UseApiBehavior = false;
opt.DefaultApiVersion = ApiVersion.Default;
opt.AssumeDefaultVersionWhenUnspecified = true;
})
.AddVersionedApiExplorer(opt =>
{
opt.GroupNameFormat = "'v'VV";
opt.SubstituteApiVersionInUrl = true;
});
Versions:
.NET 6
ASP.NET Core 6
Microsoft.AspNetCore.Mvc.Versioning 5
Actually I didn't find the way to disable it. However, I found the solution for my case. First of all, I implemented my CustomErrorResponseProvider for ErrorResponses.
public class CustomErrorResponseProvider : DefaultErrorResponseProvider
{
public override IActionResult CreateResponse(ErrorResponseContext context)
{
switch (context.ErrorCode)
{
case "UnsupportedApiVersion":
throw new UnsupportedApiVersionException("Url", context.Message);
}
return base.CreateResponse(context);
}
}
Than started throw necessary exception from it. And that's all!
As my middleware looks like this(img below), my ErrorHandlingMiddleware didn't catch an exception, because it was generated by default api versioning error handler.
I am registering user using Email/Password Sign-In-Method. So after successfully registration and loge in, I can't read and write my data in Firebase Real Time.
I'm new to Firebase so I can't really figure out what the main problem is or which steps I have missed.
This rule is obviously working fine:
{
"rules": {
".read": "auth == null",
".write": "auth == null"
}
}
However, this rule is not working, although my user is already registered and has got a UID.
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
It always shows the following error:
Exception occured while processing the request.
Url: https://App-Name-6ssd2.firebaseio.com/Appointments/-MUAG-CHWBFSv-Vrtsoc/.json?print=silent
Response: {
"error" : "Permission denied"
}
What I currently want is to allow ANY registered user to read/write everybody's information, but it always denied the permission.
Screenshots:
My implementation for getting Firebase Data.
The error is also pointing to the this method.
public async Task<List<Appointment>> GetUserAppointment(string userId)
{
var appointments = (await Firebase.Child("Appointments")
.OnceAsync<Appointment>()).Where(a => a.Object.UID == userId).Select(item =>
new Appointment
{
UID = item.Object.UID,
AppointmentID = item.Object.AppointmentID,
Title = item.Object.Title,
Date = item.Object.Date,
Time = item.Object.Time,
Status = item.Object.Status,
ReasonText = item.Object.ReasonText
}).ToList();
return appointments;
My implementation when a user SinUp for the first time.
public async Task<string> SignUpWithEmailAndPassword(string email, string password)
{
try
{
var signUpTask = await auth.CreateUserWithEmailAndPasswordAsync(email, password);
var user = signUpTask.User;
var token = await auth.CurrentUser.GetIdToken(false).AsAsync<GetTokenResult>();
await SendEmailVerification();
return token.Token;
}
catch (FirebaseAuthInvalidUserException e)
{
e.PrintStackTrace();
return string.Empty;
}
catch (FirebaseAuthInvalidCredentialsException e)
{
e.PrintStackTrace();
return string.Empty;
}
catch (FirebaseAuthUserCollisionException existEmail)
{
existEmail.PrintStackTrace();
return string.Empty;
}
}
In order to check whether a user is authenticated/verified or not, i did the debugging and as a result... I can see the same UID in my console as it is in Firebase -> Authentication -> Users.
Here is the screenshot.
Debugging GetUserAppointment(string userID) to check whether the current user is set.
It's because you are not authorized to the Database, check the Rules Tab in the Realtime database.
Open firebase, select database on the left hand side.
Now on the right hand side, select Realtime database from the dropdown and change the rules to allow anyone to write the Database
{
"rules": {
".read": true,
".write": true
}
}
Your rule is:
{
"rules": {
".read": "auth != null",
".write":"auth != null"
}
}
This means only authorized user's can write and read the Data.
Background
I have a web api server (asp.net core v2.1) that serve some basic operation, like managing entities on the server. This is the interface:
[HttpPost]
[Route("create")]
public async Task<ActionResult<NewEntityResponse>> Create(CreateEntityModel model)
{
// 1) Validate the request.
// 2) Create a new row on the database
// 3) Return the new entity in response.
}
The user running this REST method in this way:
POST https://example.com/create
Content-Type: application/json
{
"firstName": "Michael",
"lastName": "Jorden"
}
And getting response like this:
Status 200
{
"id": "123456" // The newly created entity id
}
The Problem
When sending thousands of requests like this, at some point it will fail because of network connections. When connection fails, it can leads us into two different situations:
The network call was ended on the way to the server - In this case, the server don't know about this request. Therefore, the entity wasn't created. The user just have to send the same message again.
The network call was sent from the server to back to the client but never rich the destination - In this case the request was fulfill completely, but the client don't aware for this. The expected solution is to send the same request again. In this case, it will create the same entity twice - and this is the problem.
The Requested Solution
I want to create an generic solution for web-api that "remmeber" which commands it already done. if he got same request twice, it's return HTTP status code Conflict.
Where I got so far
I thought to add the client an option to add a unique id to the request, in this way:
POST https://example.com/create?call-id=XXX
Add to my server a new filter that check if the key XXX is already fulfill. If yes, return Conflict. Otherwise - continue.
Add another server filter that checks the response of the method and marking it as "completed" for further checks.
The problem with this solution on concurrency calls. If my method takes 5 seconds to be returned and the client sent the same message again after 1 second - it will create two entities with same data.
The Questions:
Do you think that this is good approach to solve this problem?
Do you familiar with ready to use solutions that doing this?
How to solve my "concurrency" problem?
Any other tips will be great!
thanks.
Isnt the easiest solution to make the REST action idempotent?
I mean by that: the call should check if the resource already exists and either create a new resource if it doesnt or return the existing if it does?
OK, I just figure it up how to make it right. So, I implemented it by myself and share it with you.
In order to sync all requests between different servers, I used Redis as cache service. If you have only one server, you can use Dictionary<string, string> instead.
This filter do:
Before processing the request - add a new empty value key to Redis.
After the server processed the request - store the server response in Redis. This data will be used when the user will ask again for same request.
public class ConflictsFilter : ActionFilterAttribute
{
const string CONFLICT_KEY_NAME = "conflict-checker";
static readonly TimeSpan EXPIRE_AFTER = TimeSpan.FromMinutes(30);
private static bool ShouldCheck(ActionDescriptor actionDescription, IQueryCollection queries)
{
return queries.ContainsKey(CONFLICT_KEY_NAME);
}
private string BuildKey(string uid, string requestId)
{
return $"{uid}_{requestId}";
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (ShouldCheck(context.ActionDescriptor, context.HttpContext.Request.Query))
{
using (var client = RedisConnectionPool.ConnectionPool.GetClient())
{
string key = BuildKey(context.HttpContext.User.GetId(), context.HttpContext.Request.Query[CONFLICT_KEY_NAME]);
string existing = client.Get<string>(key);
if (existing != null)
{
var conflict = new ContentResult();
conflict.Content = existing;
conflict.ContentType = "application/json";
conflict.StatusCode = 409;
context.Result = conflict;
return;
}
else
{
client.Set(key, string.Empty, EXPIRE_AFTER);
}
}
}
base.OnActionExecuting(context);
}
public override void OnResultExecuted(ResultExecutedContext context)
{
base.OnResultExecuted(context);
if (ShouldCheck(context.ActionDescriptor, context.HttpContext.Request.Query) && context.HttpContext.Response.StatusCode == 200)
{
string key = BuildKey(context.HttpContext.User.GetId(), context.HttpContext.Request.Query[CONFLICT_KEY_NAME]);
using (var client = RedisConnectionPool.ConnectionPool.GetClient())
{
var responseBody = string.Empty;
if (context.Result is ObjectResult)
{
ObjectResult result = context.Result as ObjectResult;
responseBody = JsonConvert.SerializeObject(result.Value);
}
if (responseBody != string.Empty)
client.Set(key, responseBody, EXPIRE_AFTER);
}
}
}
}
The code is executed only if the query ?conflict-checker=XXX is exists.
This code is provide you under MIT license.
Enjoy the ride :)
I'm looking to connect my bot on teams channel but i didn't know the way to secure this for use only in our domains (organization).
I have test to look (authentication AAD) for the Azure Web App but it's doesn't work on teams or on webchat because the endpoint adresse it's not redirected.
I have test to implement AUTH card but it doesn't work on teams.
Note : I'm using botframework C# api BotBuilder 3.15.2.2
I have look other "ask" like :
AAD authentication in Microsoft teams for Bot Framework
Is it possible to access custom tabs in 1:1 chats in MS Teams?
https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/authentication/auth-flow-bot
https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/authentication/auth-bot-AAD
https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/authentication/authentication
https://tsmatz.wordpress.com/2016/09/06/microsoft-bot-framework-bot-with-authentication-and-signin-login/
Sincerely,
Pascal.
Edit :
I have implemented the solution sugested by Adrian, below was a piece of C# code that implement this on the MessasController.cs (Post Function):
Note ==> Adding access for localhost use
//https://stackoverflow.com/questions/51090597/botframework-on-teams-channel-11-authentication-aad-integrated
string tenantIdAAD = "";
try
{
tenantIdAAD = activity.GetChannelData<TeamsChannelData>().Tenant.Id;
}
catch (Exception exception)
{
tenantIdAAD = "";
}
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
if ([AAD_TenantID].TenantIdAAD.Equals(tenantIdAAD) || activity.ServiceUrl.StartsWith("http://localhost") )
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog().LogIfException());
}
else
{
await connector.Conversations.ReplyToActivityAsync(activity.CreateReply("Access Denied"));
}
The incoming message contains information that you can use to identify the user. A message looks like this:
{
...
"from": {
"id": "29:1XJKJMvc5GBtc2JwZq0oj8tHZmzrQgFmB39ATiQWA85gQtHieVkKilBZ9XHoq9j7Zaqt7CZ-NJWi7me2kHTL3Bw",
"name": "Richard Moe",
"aadObjectId": "ae361bee-9946-4082-99dc-6994b00ceebb"
},
"channelData": {
"tenant": {
"id": "72f988bf-86f1-41af-91ab-2d7cd011db47"
}
}
}
The channelData.tenant.id identifies the organization (O365 tenant) that the user is part of, so you can look at that and reject messages that aren't from ones that you expect.
The message also has the AAD object ID of the sender in from.aadObjectId. The auth flow in the links above is useful if you need tokens to provide to other services so you can act on behalf of the user, but if all you need is the user's tenant and identity, the message has all you need.
(Remember to authenticate the incoming request first, before trusting any information in the message.)
I have a .NET MVC5 website, where the user is logged in using Microsoft Identity. I have multiple form posts for adding and editing items across the site. I'm looking to know which order I should perform validation in:-
ModelState.IsValid and then User.Identity.IsAuthenticated
User.Identity.IsAuthenticated then ModelState.IsValid
I currently have the following code which works, but it seem to be a case of 'the chicken and egg':-
var user = UserAccountFunctions.GetUser(User);
if (user != null)
{
ClientProfile profile = ClientProfile.GetUser(user.Id, db);
if (profile != null)
{
if (ModelState.IsValid)
{
// Do logic here
}
}
}
Should I swap this code round to check the model first, before checking authentication so that I have:-
if (ModelState.IsValid)
{
var user = UserAccountFunctions.GetUser(User);
if (user != null)
{
ClientProfile profile = ClientProfile.GetUser(user.Id, db);
if (profile != null)
{
// Do logic here...
}
}
}
Or is there simply no difference here? I repeat this code a lot throughout the site, so looking for which is the better option? I currently use the top one, because I feel that you shouldn't even attempt to check the model unless they are authenticated?
Any advice here?
Thanks!
Here is example of updating users's email:
[AcceptVerbs(HttpVerbs.Post)]
[Authorize]
[ValidateAntiForgeryToken()]
public ActionResult emailupdate(UserEmailEditModel editmodel_post)
{
if (!ModelState.IsValid)
{
// redirect to email view and show errors
}
// check if posted id is the same as stored in session
if (User.Identity.GetUserId() != editmodel_post.user_id.ToString())
{
// redirect to email view and show errors
}
}
So
Use Authorize attribute
Use ValidateAntiForgeryToken attribute
Check ModelState
Check against session or database