Extracting Identity from lambda authorizer response - asp.net-core

I made a custom lambda authorizer that validates the JWT and returns Allow policy.
var context = new APIGatewayCustomAuthorizerContextOutput();
var tokenUse = ExtractClaims(claims, "token_use");
context["tokenType"] = tokenUse;
var response = new APIGatewayCustomAuthorizerResponse
{
PrincipalID = "asd",
PolicyDocument = new APIGatewayCustomAuthorizerPolicy
{
Version = "2012-10-17",
Statement = new List<APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement>()
{
new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement
{
Action = new HashSet<string>() {"execute-api:Invoke"},
Effect = "Allow",
Resource = new HashSet<string>() {"***"} // resource arn here
}
},
},
Context = context
};
return response;
Now I need to use this Identity on my resource server.
The problem is that the claims I put in the authorizer context appears under authorizer directly
"authorizer": {
"cognito:groups": "Admin", ...
}
but my Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction expects those under authorizer.claims.
like such:
"authorizer": {
"claims": {
"cognito:groups": "Admin", ...
}
}
And I know that, because it was working when I was using the built in Cognito User Pool authorizer, which was making the input like that.
I managed to find that Lambda Authorizer is not allowed to add nested objects to the context (and tested that it throws authorizer error if I do.)
I also found that when APIGatewayProxyFunction is extracting the Identity, it looks at Authorizer.Claims.
So I need to either extract them on my resource server bypassing the Claims property somehow, or add a nested object to the authorizer response, which is not allowed.
What do?

So I solved this by overriding the PostCreateContext method on my LambdaEntryPoint : Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction.
protected override void PostCreateContext(
HostingApplication.Context context,
APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext)
{
// handling output from cognito user pool authorizer
if (apiGatewayRequest?.RequestContext?.Authorizer?.Claims != null)
{
var identity = new ClaimsIdentity(apiGatewayRequest.RequestContext.Authorizer.Claims.Select(
entry => new Claim(entry.Key, entry.Value.ToString())), "AuthorizerIdentity");
context.HttpContext.User = new ClaimsPrincipal(identity);
return;
}
// handling output from lambda authorizer
if (apiGatewayRequest?.RequestContext?.Authorizer != null)
{
var identity = new ClaimsIdentity(apiGatewayRequest.RequestContext.Authorizer.Select(
entry => new Claim(entry.Key, entry.Value.ToString())), "AuthorizerIdentity");
context.HttpContext.User = new ClaimsPrincipal(identity);
}
}
Edit: also submitted a pull reqeust to the aws-lambda-dotnet library fixing this.
Edit 2: My pull request has been merged for some time and this is not an issue anymore if using an up to date Amazon.Lambda.AspNetCoreServer (not sure which version first had it, but 3.1.0 definitely has it)

Related

Restsharp107.3.0 Sequential mock execution is failing when trying InSequence operation in unittest

I am converting Restsharp .netcore3.1 version to .Net6.0 with latest version of Restsharp107.3.0.
Existing test case was failing due to the interfaces were changed to classes also RestRequestAsyncHandle is not supported in latest version.
Please show some light on this if you faced any issue like this.
Expected result: Need to mock test to validate HttpStatusCode.RequestTimeout and HttpStatusCode.OK in sequence order.
// Arrange
var restClient = new Mock<RestClient>();
// Create the MockSequence to validate the call order
var sequence = new MockSequence();
restClient
.InSequence(sequence).Setup(x => x.ExecuteAsync(
It.IsAny<RestRequest>(),
It.IsAny<Action<RestResponse, RestRequestAsyncHandle>>()))
.Callback<RestRequest, Action<RestResponse, RestRequestAsyncHandle>>
((request, callback) =>
{
callback(new RestResponse { StatusCode =
HttpStatusCode.RequestTimeout }, null);
});
restClient
.InSequence(sequence).Setup(x => x.ExecuteAsync(
It.IsAny<RestRequest>(),
It.IsAny<Action<RestResponse, RestRequestAsyncHandle>>()))
.Callback<RestRequest, Action<RestResponse, RestRequestAsyncHandle>>
((request, callback) => { callback(new RestResponse {
StatusCode = HttpStatusCode.OK }, null);});

Podio API - Session Management class error in accessing tokens in Redis

I'm trying to use Session Management for API calls, so I don't trigger Auth class function everytime my script run. I mostly used App ID authentication so I used the sample provided for Redis.
However, I'm getting an error "Fatal error: Uncaught Error: Cannot access self:: when no class scope is active in /var/www/html/authcheck.php:22 Stack trace: #0 {main} thrown in /var/www/html/authcheck.php on line 22"
The code in line 22 is this - Podio::$oauth = self::$session_manager->get(Podio::$auth_type);
Here's the PHP Script for Session manager class:
Filename: SessionManager.php
<?php
require ('podio/podio_lib/PodioAPI.php');
require ('predis/autoload.php');
class PodioRedisSession {
/**
* Create a pointer to Redis when constructing a new object
*/
public function __construct() {
$this->redis = new Predis\Client();
}
/**
* Get oauth object from session, if present. We use $auth_type as
* basis for the cache key.
*/
public function get($auth_type = null) {
// If no $auth_type is set, just return empty
// since we won't be able to find anything.
if (!$auth_type) {
return new PodioOauth();
}
$cache_key = "podio_cache_".$auth_type['type']."_".$auth_type['identifier'];
// Check if we have a stored session
if ($this->redis->exists($cache_key)) {
// We have a session, create new PodioOauth object and return it
$cached_value = $this->redis->hgetall($cache_key);
return new PodioOAuth(
$cached_value['access_token'],
$cached_value['refresh_token'],
$cached_value['expires_in'],
array("type"=>$cached_value['ref_type'], "id"=>$cached_value['ref_id'])
);
}
// Else return an empty object
return new PodioOAuth();
}
/**
* Store the oauth object in the session. We ignore $auth_type since
* it doesn't work with server-side authentication.
*/
public function set($oauth, $auth_type = null) {
$cache_key = "podio_cache_".$auth_type['type']."_".$auth_type['identifier'];
// Save all properties of the oauth object in redis
$this->redis->hmset = array(
'access_token' => $oauth->access_token,
'refresh_token' => $oauth->refresh_token,
'expires_in' => $oauth->expires_in,
'ref_type' => $oauth->ref["type"],
'ref_id' => $oauth->ref["id"],
);
}
}
?>
Filename: authcheck.php
<?php
require ('podio/podio_lib/PodioAPI.php');
include ('SessionManager.php');
$client_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
$client_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
$app_id = "xxxxxxxxxxx";
$app_token = "xxxxxxxxxxxxxxxxxxx";
Podio::setup($client_id, $client_secret, array(
"session_manager" => "PodioRedisSession"
));
// podio-php will attempt to find a session automatically, but will fail
because
// it doesn't know which $auth_type to use.
// So we must attempt to locate a session manually.
Podio::$auth_type = array(
"type" => "app",
"identifier" => $app_id
);
Podio::$oauth = self::$session_manager->get(Podio::$auth_type);
// Now we can check if anything could be found in the cache and
// authenticate if it couldn't
if (!Podio::is_authenticated()) {
// No authentication found in session manager.
// You must re-authenticate here.
Podio::authenticate_with_app($app_id, $app_token);
} else {
//echo "<pre>".print_r($_SESSION, true)."</pre>";
echo "You already authenticated!";
}
// // We can safely switch to another app now
// // First attempt to get authentication from cache
// // If that fails re-authenticate
// Podio::$auth_type = array(
// "type" => "app",
// "identifier" => $another_app_id
// );
// Podio::$oauth = self::$session_manager->get(Podio::$auth_type);
// if (!Podio::is_authenticated()) {
// // No authentication found in session manager.
// // You must re-authenticate here.
// Podio::authenticate_with_app($another_app_id, $another_app_token);
// }
?>
Hi opted not to use redis instead I used session and PDO Mysql on storing podio auth.

Authentication in laravel

I am making api in laravel. I am using the database that is already built and is live. That system uses md5 with salt values.
I want to do authentication for that system . how should do i have to do?
My source code :
public function authenticate(Request $request)
{
$email = $request->input('email');
$password = md5('testpassword' . 'saltvaluehere');
try {
//
// attempt to verify the credentials and create a token for the user
if (!$token = JWTAuth::attempt([
'email' => $email,
'pass' => $password
])
) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
// something went wrong whilst attempting to encode the token
return response()->json(['error' => 'could_not_create_token'], 500);
}
// all good so return the token
return response()->json(compact('token'));
}
In core php these authentication is done by:
$getpass = $mydb->CleanString($_POST['password']);
$getusername = $mydb->CleanString($_POST['username']);
$dbpass = $mydb->md5_decrypt($mydb->getValue("pass","tbl_admin","username = '".$getusername."'"), SECRETPASSWORD);
if($dbpass == $getpass){
return 'success full login';
}
above code doesnot give same value of hash, so i am not being able to authenticate in system.
Edited:
I have got the password that matched with database but token is not bieng generated.
here is my code:
public function authenticate(Request $request)
{
$email = $request->input('email');
$password = $request->input('password');
$password = md5($password);
try {
//
// attempt to verify the credentials and create a token for the user
if (!$token = JWTAuth::attempt([
'email' => $email,
'password' => $password
])
){
//return response()->json(compact('token'));
return response()->json(['error' => 'invalid_credentials','_data'=>[$password,$email]], 401);
}
} catch (JWTException $e) {
// something went wrong whilst attempting to encode the token
return response()->json(['error' => 'could_not_create_token'], 500);
}
// all good so return the token
return response()->json(compact('token'));
}
can anybody tell me the reason why is token not being generated and why is it saying invalid credintials. as it shows the password and email of database of encrypted form.
This is a situation I've dealt with and I have foss code to give as a proof of concept.
Essentially, I added another password field for old passwords. I imported users with password_legacy set to a JSON object of old password data, in my case:
{ 'hasher' : 'algo', 'hash' : '#####', 'salt' : 'salt here' }
Then, I used a modified user service provider so that authentication checked for password_legacy if password was null. It then used the Illuminate Hasher contract as a base and constructed a new instance of a class (dynamically, using the hasher property, so for instance a app\Services\Hashing\AlgoHasher) and then used that for authentication instead of the default system.
If it did succeed, I bcrypt'd the password, set password and unsetpassword_legacy. This upgraded the password to the much higher standard of security that bcrypt offered.
Migration code for new field:
https://github.com/infinity-next/infinity-next/blob/ccb01c753bd5cedd75e03ffe562f389d690de585/database/migrations_0_3_0/2015_06_12_181054_password_legacy.php
Modified user provider:
https://github.com/infinity-next/infinity-next/blob/ccb01c753bd5cedd75e03ffe562f389d690de585/app/Providers/EloquentUserProvider.php
Hasher contract for Vichan (md5+salt):
https://github.com/infinity-next/infinity-next/blob/ccb01c753bd5cedd75e03ffe562f389d690de585/app/Services/Hashing/VichanHasher.php
Relevant user code:
https://github.com/infinity-next/infinity-next/blob/ccb01c753bd5cedd75e03ffe562f389d690de585/app/User.php#L124-L165
Hope that helps!

Firebase make user object from auth data

So I'm using Angularfire in an ionic app and trying to figure out how to make a user object that is associated with the auth data from an Auth $createUser call. My first try had the auth call and the user got authenticated, then a user object was made and pushed into a $firebaseArray which works fine, but I don't know how to grab the current user after they are logged in to update, destory, or do anything with that users data. I have made it work with looping through the users array and matching the uid from the user array item and the auth.uid item which was set to be the same in the user array object creation. This seems really ineffecient to loop over if there is a large user array and it needs to be done on multiple pages.
My current attempt is using a different method like so:
angular.module('haulya.main')
.controller('RegisterController', ['Auth', '$scope', 'User', '$ionicPlatform', '$cordovaCamera','CurrentUserService',
function(Auth, $scope, User, $ionicPlatform, $cordovaCamera, CurrentUserService) {
//scope variable for controller
$scope.user = {};
console.log(User);
$scope.createUser = function(isValid) {
var userModel;
$scope.submitted = true;
//messages for successful or failed user creation
$scope.user.message = null;
$scope.user.error = null;
//if form is filled out valid
if(isValid) {
//Create user with email and password firebase Auth method
Auth.$createUser({
email: $scope.user.email,
password: $scope.user.password
})
.then(function(userData) {
userModel = {
uid: userData.uid,
photo: $scope.user.photo || null,
firstName: $scope.user.firstName,
lastName: $scope.user.lastName,
email: $scope.user.email,
cell: $scope.user.cell,
dob: $scope.user.dob.toString(),
city: $scope.user.city,
state: $scope.user.state,
zip: $scope.user.zip
}
// add new user to profiles array
User.create(userModel).then(function(user) {
$scope.sharedUser = User.get(user.path.o[1]);
});
$scope.user.message = "User created for email: " + $scope.user.email;
})
.catch(function(error) {
//set error messages contextually
if(error.code == 'INVALID_EMAIL') {
$scope.user.error = "Invalid Email";
}
else if(error.code == 'EMAIL_TAKEN'){
$scope.user.error = "Email already in use, if you think this is an error contact an administrator";
}
else {
$scope.user.error = "Fill in all required fields";
}
});
}
};
//Get profile pic from camera, or photo library
$scope.getPhoto = function(type) {
$ionicPlatform.ready(function() {
//options for images quality/type/size/dimensions
var options = {
quality: 65,
destinationType: Camera.DestinationType.DATA_URL,
sourceType: Camera.PictureSourceType[type.toUpperCase()],
allowEdit: true,
encodingType: Camera.EncodingType.JPEG,
targetWidth: 100,
targetHeight: 100,
popoverOptions: CameraPopoverOptions,
saveToPhotoAlbum: false
};
//get image function using cordova-plugin-camera
$cordovaCamera.getPicture(options)
.then(function(photo) {
$scope.user.photo = photo;
}, function(err) {
console.log(err);
});
});
};
}]);
And here's the service the controller is using:
angular
.module('haulya.main')
.factory('User', function($firebaseArray) {
var ref = new Firebase('https://haulya.firebaseio.com');
var users = $firebaseArray(ref.child('profiles'));
var User = {
all: users,
create: function(user) {
return users.$add(user);
},
get: function(userId) {
return $firebaseArray(ref.child('profiles').child(userId));
},
delete: function(user) {
return users.$remove(user);
}
};
return User;
});
This also works, but again I don't have a solid reference to the currently logged in users object data from the array. The objects id is only stored on the controllers scope.
I looked through other posts, but they were all using older versions of firebase with deprecated methods.
If you're storing items that have a "natural key", it is best to store them under that key. For users this would be the uid.
So instead of storing them with $add(), store them with child().set().
create: function(user) {
var userRef = users.$ref().child(user.uid);
userRef.set(user);
return $firebaseObject(userRef);
}
You'll note that I'm using non-AngularFire methods child() and set(). AngularFire is built on top of Firebase's regular JavaScript SDK, so they interoperate nicely. The advantage of this is that you can use all the power of the Firebase JavaScript SDK and only use AngularFire for what it's best at: binding things to Angular's $scope.
Storing user data is explained in Firebase's guide for JavaScript. We store them under their uid there too instead of using push(), which is what $add() calls behind the scenes.

How do I define the response to a missing claim in NancyFX?

I have a module meant to enable administrators to manage users. Of course, this module requires not only authentication but also a specific claim. I have discovered that, if you are missing the claim in question, you actually get only a blank page as a response to your request. This isn't ideal.
How can I change that?
Module code below (if anyone needs to look)...
public class UserModule : NancyModule
{
public UserModule()
: base("/users")
{
this.RequiresAnyClaim(new[] { "evil-dictator" });
Get["/"] = _ =>
{
ViewBag.UserName = Context.CurrentUser.UserName;
return Negotiate.WithView("Index");
};
// Generate an invitation for a pre-approved user
Get["/invite"] = _ =>
{
throw new NotImplementedException();
};
}
}
You can use an After hook to alter the response in case the claim is missing. Note that the response you get when you do not have the required claim has HTTP status code 403 Forbidden. Check for that in the After hook and alter the response as needed.
E.g. the following will redirect to the root - "/" - of the application, when the user does have the evil dictator claim:
public class UserModule : NancyModule
{
public UserModule()
: base("/users")
{
After += context =>
{
if (ctx.Response.StatusCode == HttpStatusCode.Forbidden)
ctx.Response = this.Response.AsRedirect("/");
}
this.RequiresAnyClaim(new[] { "evil-dictator" });
Get["/"] = _ =>
{
ViewBag.UserName = Context.CurrentUser.UserName;
return Negotiate.WithView("Index");
};
// Generate an invitation for a pre-approved user
Get["/invite"] = _ =>
{
throw new NotImplementedException();
};
}
}