How to prevent a node from being deleted using Firebase Database rules? - database

I have a firebase database, with only 3 main nodes. This is a chat app and below is the structure
chat_messages
- chatID
-messageID
-message (string)
-read (bool)
-senderName (string)
-senderProfilePic (string)
-sent_by (string)(uid)
-timestamp (string)
chat_room
-chatID
-chat_mode (string)
-last_message (string)
-timestamp (string)
-members
-uid (string)
-email (string)
-name (string)
-photo (string)
-uid (string)
-userID (int)
-userRole (string)
user_chatrooms
-uid
-chatID( string) : timestamp (string)
I set the basic security, allowing read and write to true and I received an email from firebase saying my database is having insecure rules
Then I re-configured the database rules as follows
{
"rules": {
"chat_messages":
{
".read": "auth != null",
".write": "auth != null",
},
"chat_room":
{
".read": "auth != null",
".write": "auth != null",
},
"user_chatrooms":
{
".read": "auth != null",
".write": "auth != null",
}
}
}
This provides an extra protection by blocking access to anything else outside the mentioned nodes.
now how can I prevent these nodes from being deleted?

To prevent a node from being deleted you can check whether new data is provided in write operations:
".write": "auth !== null && newData.exist()"

Related

Cant fix my if-else condition in login.php

I have created a condition to check if name and password are correct the only issue here is when i test it it goes complete the opposite of what i want. It doesnt matter if i put it right or wrong the message will always be "You have successfuly logged in". Im using PDO just to know
<?php
include('connection.php');
$name = $_POST['name'];
$password = $_POST['password'];
$data = $_POST;
$statment = $connection->prepare('SELECT * FROM registration WHERE name = :name AND password = :password');
if($statment){
$result = $statment->execute([
':name' => $data['name'],
':password' => $data['password']
]);
}
if($data['name'] == $name && $data['password'] == $password){
echo 'You have successfuly logged in';
}else {
die('Incorrect username or password');
}
?>
You have made your script overly complicated .. The easiest way is to bind and execute .. Then you can simply check if there are any rows, and THEN compare with your data array created from the executed statement.
<?php
include('connection.php');
$name = $_POST['name'];
$password = $_POST['password'];
$statment = $connection->prepare("SELECT name, password FROM registration
WHERE name = ? AND password = ?");
$statment ->bind_param("ss", $name, $password);
$statment ->execute();
$result = $stmt->get_result();
if ($result ->num_rows > 0) {
$data = $result->fetch_array();
}else{
echo "No results found";
}
if($data['name'] == $name && $data['password'] == $password){
echo 'You have successfuly logged in';
}else {
die('Incorrect username or password');
}
?>
(bear in mind I wrote that freehand, and it has not been tested or debugged, but the principals are there)
ON A SIDE NOTE
That being said .. You should never be storing passwords in a open "text" field. You should be encrypting them. The easiest way is to use bcrypt to build out a hash:
$options = [
'cost' => 12,
];
$newPass = password_hash($pass, PASSWORD_BCRYPT, $options);
And store that in your database .. Then you can compare it like so ..
if (password_verify($pss, $pwdCheck)
$pss being what was sent in from the form .. and $pwdCheck being the hash you SELECTED from the database -- Brought into your current code set, would look something like:
if($data['name'] == $name && password_verify($password, $data['password']){

Blazor cascading AuthorizeView Policy not working

I'm working on a new project that will have some in depth policies for what user can and can't access/see, with Identity Server 4.
I'm trying to use AuthorizeView with policies to hide options in my navigation, but the views are cascading, meaning I have something like this:
<MatNavMenu>
<MatNavItem Href="/home" Title="Home"><MatIcon Icon="#MatIconNames.Home"></MatIcon> Home</MatNavItem>
<MatNavItem Href="/claims" Title="Claims"><MatIcon Icon="#MatIconNames.Vpn_key"></MatIcon> Claims</MatNavItem>
<AuthorizeView Policy="#PolicyNames.IdentitySystemAccess">
<Authorized>
<AuthorizeView Policy="#PolicyNames.AccessManagement">
<Authorized>
<MatNavSubMenu #bind-Expanded="#_accessSubMenuState">
<MatNavSubMenuHeader>
<MatNavItem AllowSelection="false"> Access Management</MatNavItem>
</MatNavSubMenuHeader>
<MatNavSubMenuList>
<AuthorizeView Policy="#PolicyNames.User">
<Authorized>
<MatNavItem Href="users" Title="users"><MatIcon Icon="#MatIconNames.People"></MatIcon> Users</MatNavItem>
</Authorized>
</AuthorizeView>
<AuthorizeView Policy="#PolicyNames.Role">
<Authorized>
<MatNavItem Href="roles" Title="roles"><MatIcon Icon="#MatIconNames.Group"></MatIcon> Roles</MatNavItem>
</Authorized>
</AuthorizeView>
</MatNavSubMenuList>
</MatNavSubMenu>
</Authorized>
</AuthorizeView>
</Authorized>
</AuthorizeView>
I have checked that the claims required to fulfil the defined policies are present after the user is logged in, but for some reason the AuthorizeView isn't working.
I have updated my App.Razor to use AuthorizeRouteView. Any ideas as to why this is happening?
Note: I am using claims that are assigned to a role, but these are dynamic and I cannot use policy.RequireRole("my-role") in my policies, thus is use:
options.AddPolicy(PolicyNames.User, b =>
{
b.RequireAuthenticatedUser();
b.RequireClaim(CustomClaimTypes.User, "c", "r", "u", "d");
});
When my app runs, none of the items in the menu show up except for the home and claims items which are not protected by an AuthorizeView.
The issue was due to the current lack of support for Blazor to read claims the are sent as arrays.
e.g. user: ["c","r","u","d"]
Can't be read.
To rectify this you need to add ClaimsPrincipalFactory.
e.g.
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
namespace YourNameSpace
{
public class ArrayClaimsPrincipalFactory<TAccount> : AccountClaimsPrincipalFactory<TAccount> where TAccount : RemoteUserAccount
{
public ArrayClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor)
: base(accessor)
{ }
// when a user belongs to multiple roles, IS4 returns a single claim with a serialised array of values
// this class improves the original factory by deserializing the claims in the correct way
public async override ValueTask<ClaimsPrincipal> CreateUserAsync(TAccount account, RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);
var claimsIdentity = (ClaimsIdentity)user.Identity;
if (account != null)
{
foreach (var kvp in account.AdditionalProperties)
{
var name = kvp.Key;
var value = kvp.Value;
if (value != null &&
(value is JsonElement element && element.ValueKind == JsonValueKind.Array))
{
claimsIdentity.RemoveClaim(claimsIdentity.FindFirst(kvp.Key));
var claims = element.EnumerateArray()
.Select(x => new Claim(kvp.Key, x.ToString()));
claimsIdentity.AddClaims(claims);
}
}
}
return user;
}
}
}
Then register this in your program/startup(depending on if you use .core hosted or not)like so:
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("oidc", options.ProviderOptions);
})
.AddAccountClaimsPrincipalFactory<ArrayClaimsPrincipalFactory<RemoteUserAccount>>();
After understanding the problem with Steve I did the following solution.
Useful for those who follow Cris Sainty's documentation
I update my method to parse claims from jwt to separate all claim's array!
private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var claims = new List<Claim>();
var payload = jwt.Split('.')[1];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);
if (roles != null)
{
if (roles.ToString().Trim().StartsWith("["))
{
var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());
foreach (var parsedRole in parsedRoles)
{
claims.Add(new Claim(ClaimTypes.Role, parsedRole));
}
}
else
{
claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
}
keyValuePairs.Remove(ClaimTypes.Role);
}
claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
for (int i = 0; i < claims.Count; i++)
{
var name = claims[i].Type;
var value = claims[i].Value;
if (value != null && value.StartsWith("["))
{
var array = JsonSerializer.Deserialize<List<string>>(value);
claims.Remove(claims[i]);
foreach (var item in array)
{
claims.Add(new Claim(name, item));
}
}
}
return claims;
}
Adding to the above answers you can avoid it becoming array claims by having different keys for claims creation like this:
var claims = new[]
{
new Claim("UserType1", "c"),
new Claim("UserType2", "r")
....
};

How do I retrieve the 'id' of an entry/row from an 'item' in the entry/row?

Building an app using React Native (for iOS) using AWS Amplify
I want to do something seemingly so simple, but i am a bit lost as to how to do it: I have a table with user information already in it. Here's the Schema:
type Users #model {
id: ID!
userName: String
firstname: String
weblink: String
email: String
mobileNum: String
.
.
.
}
//**Here's my current Query.js**
export const getUsers = `query GetUsers($id: ID!) {
getUsers(id: $id) {
id
userName
firstname
weblink
email
.
.
.
}
}
`;
This table is populated in DynamoDB when i check my AWS console. What i need is to be able to get the id from the table using the userName (not vice versa). The id is generated when i createUser() and it's used throughout my app to get all my user's information. However when a user signs in on a new phone, this id isn't available anymore. So when they sign in via Cognito, i do know the userName and all i need to do is retrieve this id. Because there's only one unique userName, it should only return one id
Here's what i'm thinking so far: use a GSI (global secondary index). So change my schema to:
type Users #model
#key(
name: "ByUsername"
fields: ["userName"]
queryField: "getIdFromUserName"
)
{
id: ID!
userName: String
firstname: String
weblink: String
email: String
mobileNum: String
.
.
.
}
Then call in my app:
const data = await API.graphql(graphqlOperation(getIdFromUserName, { userName }));
5 questions:
1) Is there a simpler way than GSI?
2) Is that how you add the GSI? Or is it more robust to do it in the AWS console?
3) What should my Query.js then look like?
4) Do i need to make a custom resolver, or is this sufficient?
5) Am i missing anything else, or can i just
amplify push ?
//11/04/2020
//Resolver
## [Start] Prepare DynamoDB PutItem Request. **
$util.qr($context.args.input.put("createdAt", $util.defaultIfNull($ctx.args.input.createdAt, $util.time.nowISO8601())))
$util.qr($context.args.input.put("updatedAt", $util.defaultIfNull($ctx.args.input.updatedAt, $util.time.nowISO8601())))
$util.qr($context.args.input.put("__typename", "Users"))
#set( $condition = {
"expression": "attribute_not_exists(#id)",
"expressionNames": {
"#id": "id"
}
} )
#if( $context.args.condition )
#set( $condition.expressionValues = {} )
#set( $conditionFilterExpressions = $util.parseJson($util.transform.toDynamoDBConditionExpression($context.args.condition)) )
$util.qr($condition.put("expression", "($condition.expression) AND $conditionFilterExpressions.expression"))
$util.qr($condition.expressionNames.putAll($conditionFilterExpressions.expressionNames))
$util.qr($condition.expressionValues.putAll($conditionFilterExpressions.expressionValues))
#end
#if( $condition.expressionValues && $condition.expressionValues.size() == 0 )
#set( $condition = {
"expression": $condition.expression,
"expressionNames": $condition.expressionNames
} )
#end
{
"version": "2017-02-28",
"operation": "PutItem",
"key": #if( $modelObjectKey ) $util.toJson($modelObjectKey) #else {
"id": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.args.input.id, $util.autoId()))
} #end,
"attributeValues": $util.dynamodb.toMapValuesJson($context.args.input),
"condition": $util.toJson($condition)
}
## [End] Prepare DynamoDB PutItem Request. **
1) You dont need to create GSI (global secondary index).
2) You can update your createUser resolver, instead of using $util.autoId() you can pass $ctx.args.input.userName as id

CakePHP: How do I access the Auth->allow action array?

I've been working on an application using CakePHP 2.6. We have a class called AuthUser which builds upon the functionality of AuthComponent and allows us to check permissions against our roles for sections in our database.
However I have noticed that our "isAuthorised" function ignores the $this->Auth->allow() which means actions that shouldn't need authorisation are being caught by our checks and this needs to be updated to check properly.
Is it possible to access the $this->Auth->allow() array of actions and if so how would someone go about accessing it?
Below I have included the "isAuthorised" function from the AuthUser class:
public function isAuthorised($controllerName = null) {
//Admin has access to everything
if (AuthUser::isAdmin() === true) {
return true;
}
$roles = array();
//Get the roles allowed for the section
$results = AppController::runStoredProcedure('spGetCurrentSectionRolesForSectionBySectionName', array( $controllerName ));
if (isset($results) && is_array($results)) {
foreach ($results as $row) {
if (isset($row['RoleName'])) {
array_push($roles, $row['RoleName']);
}
}
}
//Check if authenticated user has permission to current controller (is one of the allowed roles)
$userRoles = AuthComponent::user('role');
if (isset($userRoles) && is_array($userRoles)) {
foreach ($userRoles as $key => $value) {
if ($value == true) {5
if (in_array($key, $roles)) {
return true;
}
}
}
}
return false;
}
Please try this
pr($this->Auth->allowedActions);
This will list you all auth->allow() function name that are defined in $this->Auth->allow()

How to match fields in cakephp that are not present in database

How to match new_password and confirm_password fields when they are not in database?
Hi... I would like to know how I would be able to match my fields "new_password" and "confirm_password", they are not stored in database they are just used for matching purpose in Change Password module.
I tried this but it didn't worked:
if($this->data['User']['new_password'] != $this->data['User']['confirm_password'] ) {
$this->Session->setFlash("New password and Confirm password field do not match");
} else {
$this->data['User']['password'] = $this->data['User']['new_password'];
$this->data['User']['id'] = $this->User->id;
if($this->User->save($this->data)) {
$this->Session->setFlash("Password updated");
$this->redirect('/users/login');
}
}
You can try this:
// check for empty value
if( !empty( $this->data['User']['new_password'] ) && !empty( $this->data['User']['confirm_password'] ) ) {
if( $this->data['User']['new_password'] != $this->data['User']['confirm_password']) {
$this->Session->setFlash("New password and Confirm password field do not match");
} else {
$this->data['User']['password'] = $this->data['User']['new_password'];
$this->data['User']['id'] = $this->User->id;
if( $this->User->save($this->data) ) {
$this->Session->setFlash("Password updated");
$this->redirect('/users/login');
} else {
$this->Session->setFlash('Password update fail');
}
}
} else {
$this->Session->setFlash('Enter New password and Confirm password');
}

Resources