Mobile Development - Permissions - mobile

I want to add functionality to check phone details in an app available on the play store, however I'm having problems related to permissions and I don't know how to solve it, here's the code:
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.READ_PHONE_STATE),
111
)
} else {
getTelephonyDetail()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if((requestCode==111) && (grantResults[0]==PackageManager.PERMISSION_GRANTED)){
getTelephonyDetail()
}
}
private fun getTelephonyDetail() {
val tm: TelephonyManager = getSystemService(TELEPHONY_SERVICE) as TelephonyManager
if (ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.READ_PHONE_STATE
) != PackageManager.PERMISSION_GRANTED
) {
return
}

Related

kotlin Firebase Variable path

Quick question.
While I write away my data to my firebase database I'd like to order it.
since i'm making an Album collection list I'd love to have it like the picture underneath.. but with bands and albums tho.
the only problem is that I can't read my write string.. in this case $Uname
I can only read the data correctly if I hardcode the location in my .getReference()
my data upload code:
//values
val number = Number_tv.text.toString()
val Uname = Username_TV.text.toString()
val ref = FirebaseDatabase.getInstance().getReference("/userdata/$Uname/$number _key")
val user = User(Username_TV.text.toString(), Email_TV.text.toString(), albumimageURL)
//code
if (number.isEmpty()){
Toast.makeText(this, "Number is empty", Toast.LENGTH_SHORT).show()
}
else{
ref.setValue(user)
.addOnSuccessListener {
Log.d("mainacti", "upload succesful")
}
.addOnFailureListener {
Log.d("mainacti", "I do not work")
}}
}
my data download code:
val ref = FirebaseDatabase.getInstance().getReference("/userdata/Heinz")
ref.addListenerForSingleValueEvent(object: ValueEventListener{
override fun onDataChange(p0: DataSnapshot) {
val adapter = GroupAdapter<GroupieViewHolder>()
p0.children.forEach {
Log.d("Albumlist", it.toString())
val user = it.getValue(User::class.java)
if (user != null){
adapter.add(UserItem(user))
}
}
recyclerview_album.adapter = adapter
}
override fun onCancelled(error: DatabaseError) {
}
})
}
Both codes are in different classes
here's a Log for the reference ("/userdata")
2021-04-16 13:00:42.080 6914-6914/com.example.firetrier D/Albumlist: DataSnapshot { key = Hans, value = {1 _key={albumimageURL=https://firebasestorage.googleapis.com/v0/b/fireupload-4be26.appspot.com/o/albumcover%2Fcb06a4c4-8795-4103-99ef-c2bd55498b2e?alt=media&token=7d3d305e-c266-41fb-b466-cab548fbdd35, email=hans#test.com, username=Hans}} }
2021-04-16 13:00:42.088 6914-6914/com.example.firetrier D/Albumlist: DataSnapshot { key = Heinz, value = {1 _key={albumimageURL=https://firebasestorage.googleapis.com/v0/b/fireupload-4be26.appspot.com/o/albumcover%2Ff00f6651-9ccf-4533-8e1c-af103a1a17e6?alt=media&token=b6e4ad2f-cb4f-47d5-8f00-02bdc4765c00, email=Heinz#doof.com, username=Heinz}, 5 _key={albumimageURL=https://firebasestorage.googleapis.com/v0/b/fireupload-4be26.appspot.com/o/albumcover%2Fc9bb80aa-15a2-48cb-b6f5-6b7a59b11d8d?alt=media&token=facd516d-c3e3-4b61-aa09-23ea9062ccd3, email=hans#test.com, username=Heinz}} }
is there a way to use the value instead of the key?
if I adapt the reference to one of the usernames this is the log
2021-04-16 13:05:54.028 7629-7629/com.example.firetrier D/Albumlist: DataSnapshot { key = 1 _key, value = {albumimageURL=https://firebasestorage.googleapis.com/v0/b/fireupload-4be26.appspot.com/o/albumcover%2Ff00f6651-9ccf-4533-8e1c-af103a1a17e6?alt=media&token=b6e4ad2f-cb4f-47d5-8f00-02bdc4765c00, email=Heinz#doof.com, username=Heinz} }
2021-04-16 13:05:54.040 7629-7629/com.example.firetrier D/Albumlist: DataSnapshot { key = 5 _key, value = {albumimageURL=https://firebasestorage.googleapis.com/v0/b/fireupload-4be26.appspot.com/o/albumcover%2Fc9bb80aa-15a2-48cb-b6f5-6b7a59b11d8d?alt=media&token=facd516d-c3e3-4b61-aa09-23ea9062ccd3, email=hans#test.com, username=Heinz} }

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")
....
};

Getting 400 from samltest.id when attempting SP-initiated worflow

I have used itfoxtec's SAML2 library to implement an SP in my ASP.NET MVC app. I am testing using samltest.id as the IdP. The IdP-initiated workflow works perfectly, but the SP-initiated workflow always gets a 400 error back from samltest.id. I have attempted to look through samltest.id's log to see if an error is being recorded there for my request, but I cannot seem to find anything there.
This is the Action that handles the URL that he user would go to when initiating SSO:
public ActionResult SSOLogin() {
LogManager logger = new LogManager("SSOLogin");
string hostname = this.GetHostname();
SchoolSettings settings = this.GetClientSettings();
if (settings.UseSAMLSSO) {
Saml2Configuration samlConfig = null;
try {
samlConfig = SamlConfigLoader.GetSaml2Config(HttpContext, settings, this.IsSandbox());
} catch (Exception e) {
logger.exception($"loading Saml2Configuration for {hostname}", e);
}
if (samlConfig != null) {
try {
var binding = new Saml2RedirectBinding();
binding.SetRelayStateQuery(new Dictionary<string, string> { { "Home/Index", Url.Content("~/") } });
return binding.Bind(new Saml2AuthnRequest(samlConfig) {
}).ToActionResult();
} catch (Exception e) {
logger.error($"Exception redirecting to IdP. {e.GetType().ToString()}: {e.Message}\n{e.StackTrace}");
ViewBag.ssoerror = $"Error redirecting to IdP for {hostname}";
}
} else {
logger.critical($"Could not load SAML2 configuration for {hostname}");
ViewBag.ssoerror = $"Could not load SAML2 configuration for {hostname}";
}
} else {
ViewBag.ssoerror = "SSO is not configured for this client. Please contact Support";
}
return Redirect("/Home/SSOError");
}
The method that loads a client-specific metadata looks like this:
public static Saml2Configuration GetSaml2Config(HttpContextBase context, SchoolSettings forSchool, bool forSandbox) {
LogManager log = new LogManager("getSaml2Config");
Saml2Configuration config = new Saml2Configuration();
if (!forSandbox) {
config.Issuer = _saml2Issuer;
} else {
config.Issuer = _saml2IssuerSandbox;
}
config.SignatureAlgorithm = _saml2SignatureAlgo;
config.CertificateValidationMode = X509CertificateValidationMode.None;
config.RevocationMode = (X509RevocationMode)Enum.Parse(typeof(X509RevocationMode), ConfigurationManager.AppSettings["Saml2:RevocationMode"]);
config.AllowedAudienceUris.Add(config.Issuer);
var entityDescriptor = new EntityDescriptor();
if (forSchool.SAMLMetadataLocationIsUrl) {
try {
entityDescriptor.ReadIdPSsoDescriptorFromUrl(new Uri(forSchool.SAMLMetadataLocation));
} catch (Exception e) {
log.error($"Exception caught loading metadata from school {forSchool.Hostname} at URL {forSchool.SAMLMetadataLocation}\n Exception {e.GetType().ToString()}: {e.Message}\n{e.StackTrace}");
entityDescriptor.IdPSsoDescriptor = null;
}
} else {
var schoolMetadataPath = context.Server.MapPath("~/App_Data/SAMLMetadata/" + forSchool.SAMLMetadataLocation);
log.info($"Loading metadata for school {forSchool.Hostname} from file {schoolMetadataPath}");
try {
entityDescriptor.ReadIdPSsoDescriptorFromFile(schoolMetadataPath);
} catch (IOException ioe) {
log.error($"IOException caught loading metadata for school {forSchool.Hostname} from file {schoolMetadataPath}: {ioe.Message}\n{ioe.StackTrace}");
entityDescriptor.IdPSsoDescriptor = null;
} catch (Exception e) {
log.error($"Exception caught loading metadata for school {forSchool.Hostname} from file {schoolMetadataPath}\n Exception {e.GetType().ToString()}: {e.Message}\n{e.StackTrace}");
entityDescriptor.IdPSsoDescriptor = null;
}
}
if (entityDescriptor.IdPSsoDescriptor != null) {
if (entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.Count() > 0) {
config.SingleSignOnDestination = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.First().Location;
} else {
log.error($"WARNING: metadata for {forSchool.Hostname} does not have any SingleSignOnServices that could be parsed.");
}
if (entityDescriptor.IdPSsoDescriptor.SingleLogoutServices.Count() > 0) {
config.SingleLogoutDestination = entityDescriptor.IdPSsoDescriptor.SingleLogoutServices.First().Location;
} else {
log.error($"WARNING: metadata for {forSchool.Hostname} does not have any SingleLogoutServices that could be parsed.");
}
if (entityDescriptor.IdPSsoDescriptor.SigningCertificates.Count() > 0) {
config.SignatureValidationCertificates.AddRange(entityDescriptor.IdPSsoDescriptor.SigningCertificates);
} else {
log.error($"WARNING: metadata for {forSchool.Hostname} does not have any SigningCertificates that could be parsed.");
}
} else {
throw new Exception("IdPSsoDescriptor not loaded from metadata.");
}
return config;
}
If it would help to clarify the situation, I can add the code for the AssertionConsumerService Action which works perfectly in an IdP-initiated scenario.
I discovered the problem. It comes down to this line of code in the GetSaml2Config method:
config.SingleSignOnDestination = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.First().Location;
This naively takes the first SingleSignOnService element in the metadata and decides that it is the correct one to use, but that was not always the case that this assumption was true. What I really wanted was to get a SingleSignOnService element for and HTTP-POST binding:
config.SingleSignOnDestination = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.Where(s => s.Binding.ToString().IndexOf("HTTP-POST") > 0).FirstOrDefault()?.Location;
This works well for all of the cases that I have found since.
Your code looks correct.
It is probably an integration issue but very hard to find if the IdP do not log an error message.
What error status message do you get back instead of success, maybe that tells you something.
Maybe the IdP do not accept the SAML 2.0 Authn Response, here is something to look for:
The config.SingleSignOnDestination probably is required
Meybe the IdP requere the request to be signed
It is also possible to add other attributes in the request, do the IdP documentation describe any requirements?

ACL allow actions not working in Cakephp 2.x

I have a multi step travelrequest app. Users are directed to the 1st step e.g localhost/intraweb/travel_requests/step/1 up until they get to the last step localhost/intraweb/travel_requests/step/5. Problem that i am having now is only administrators can access the steps and normal users cant. In my case users have an ID = 10 in my groups table
This is how i am using ACL in my UsersController
//allow users to do a travel request
public function initDB() {
$group = $this->User->Group;
$group->id = 10;
$this->Acl->allow($group, 'controllers/TravelRequests/step($stepNumber');
}
here is my code for my TravelRequestsController
public function beforeRender() {
parent::beforeRender();
$params = $this->Session->read('form.params');
$this->Auth->allow('step($stepNumber)');
$this->set('params', $params);
}
public function setup() {
$steps = 5;
$this->Session->write('form.params.steps', $steps);
$this->Session->write('form.params.maxProgress', 0);
$this->redirect(array('action' => 'step', 1));
}
public function step($stepNumber) {
if($this->Session->read('form.params.steps') != 5) {
$this->redirect(array('action'=>'index'));
}
if (!file_exists(APP.'View'.DS.'TravelRequests'.DS.'step_'.$stepNumber.'.ctp')) {
$this->redirect('/travel_requests/index');
}
$maxAllowed = $this->Session->read('form.params.maxProgress') + 1;
if ($stepNumber > $maxAllowed) {
$this->redirect('/travel_requests/step/'.$maxAllowed);
} else {
$this->Session->write('form.params.currentStep', $stepNumber);
}
}
Does some have an idea on what i am missing out in my code?
Thank you in advance
You are trying to allow $this->Acl->allow($group, 'controllers/TravelRequests/step($stepNumber'); where step($stepNumber is not an existing action.
You should use $this->Acl->allow($group, 'controllers/TravelRequests/step') instead.

Create ProfileProperty Through Code in DNN

How to Create Profile Property Through Code in DNN (DotNetNuke)?
I tried this code:
DotNetNuke.Entities.Profile.ProfilePropertyDefinition def =
DotNetNuke.Entities.Profile.ProfileController.GetPropertyDefinitionByName(this.PortalId, "Level");
if (def != null)
{
def.DataType = 10;
def.Length = 40;
def.PropertyValue = "Level";
def.PropertyName = "Level";
oUser.Profile.ProfileProperties.Add(def);
}
oUser.Profile.SetProfileProperty("Level", ddlLevel.SelectedItem.Text.ToString().Trim());
DotNetNuke.Entities.Profile.ProfileController.UpdateUserProfile(oUser, oUser.Profile.ProfileProperties);
But it won't work, please help me with suitable solution.
try out this code for adding Profile Property:
if (DotNetNuke.Entities.Profile.ProfileController.GetPropertyDefinitionByName(this.PortalId, "Level") == null)
{
DotNetNuke.Entities.Profile.ProfileController.AddPropertyDefinition(
new DotNetNuke.Entities.Profile.ProfilePropertyDefinition(this.PortalId)
{
PropertyName = "Name",
DataType = 10,
...
});
}

Resources