ICloudStorage.GetChildren() never return - xamarin.android

I'm trying to access Google Drive with CloudRail using the following codes.
// Actual string value removed.
private const string GDRIVE_CLIENT_ID = "client_id";
private const string ANDROID_PACKAGE_NAME = "package_name";
private const string CLOUDRAIL_APP_KEY = "app_key";
private readonly string REDIRECT_URL = $"{ANDROID_PACKAGE_NAME}:/oauth2redirect";
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set CloudRail application key.
CloudRail.AppKey = CLOUDRAIL_APP_KEY;
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
try
{
var googleDrive = new GoogleDrive(this, GDRIVE_CLIENT_ID, "", REDIRECT_URL, "state");
googleDrive.UseAdvancedAuthentication();
m_Service = googleDrive;
Action act = () =>
{
var list = m_Service.GetChildren(#"/");
// The function call never return.
};
var thread = new Thread(new ThreadStart(act));
thread.Start();
}
catch (Exception ex)
{
}
}
After calling into ICloudStorage.GetChildren(), my apps get redirected to login into Google account. After user has logged in to Google account and granted consent to the application, the function call never return. No exception is caught either.
What could have go wrong?

I got a reply from CloudRail support team and manage to solved the issue with their help.
You need to include the IntentFilter and LaunchMode to SingleTask on top of your activity.
Also you need to put OnNewIntent method as shown below:
[Activity(Label = "Awesome.Android", LaunchMode = Android.Content.PM.LaunchMode.SingleTask)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataScheme = "Android_Package_Name")]
public class MainActivity : Activity
{
protected override void OnNewIntent(Intent intent)
{
CloudRail.AuthenticationResponse = intent;
base.OnNewIntent(Intent);
}
}
The key is that the corresponding Activity class (in my case, MainActivity) has to be decorated with IntentFilter. Also, OnNewItent has to be overridden and passing the response back to CloudRail.AuthenticationResponse in the override.
I also found that android package name must be in full lower case, otherwise it won't work either.
Edit [2018.07.19]
Android package name must contain at least one period character (.), otherwise, you may encounter invalid_request - missing authority error.

Related

Identity Server 4 Multi-tenancy logout

I'm currently developing an identity server. It is multi-tenant with multiple user repositories.
I am able to pass (using Services.OpenIDConnect.Options) my tenant details from my MVC to the IDS in order to select the appropriate user repository on login
options.Events.OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SetParameter("Tenant", "TenantDetail");
return Task.CompletedTask;
};
I am attempting to obtain this same information for logout, however the initial call to logout has some back end process that calls CustomProfileService.IsActiveAsync(IsActiveContext context).
I am unable to obtain the tenant information from the IsActiveContext, nor am i able to read any kind of query string (as i was using for login).
Any suggestions, or even alternative methods that might be more correct than what I'm attempting, would be greatly appreciated.
OnRedirectToIdentityProvider will not be hit on signout. You'll need to pass the tenant information in the OnRedirectToIdentityProviderForSignOut event in your client instead.
Here's a snippet, that's far from complete:
services
.AddOpenIdConnect("oidc", options =>
{
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProviderForSignOut = context =>
{
context.ProtocolMessage.AcrValues = "tenant:TenantDetail";
return Task.CompletedTask;
},
}
}
In IdentityServer you'll need to lookup the acr_values in the query parameters from the request. Inject IHttpContextAccessor in order to access the context:
public class ProfileService : IProfileService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ProfileService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// ...
}
public async Task IsActiveAsync(IsActiveContext context)
{
// Please note that this method is called on many occasions. Check context.Caller
// This means that you'll have to make sure that the acr_valus are present on all
// ocassions, hence the question in my comment.
var request = _httpContextAccessor.HttpContext.Request;
if (request.Method == HttpMethods.Get)
{
// acr_values should be present on all ocassions.
var values = (string)request.Query["acr_values"];
// This is just a sample, you'll need to parse the values.
var tenant = values.Split(':')[1];
}
// Your code where you link the repository ...
var sub = context.Subject.GetSubjectId();
var user = await userManager.FindByIdAsync(sub);
context.IsActive = user != null;
}
}
Please let me know if this solves the issue for you.

Xamarin IOS Facebook SDK LoginButton

Im new to Xamarin and IOS development and dont understand why I get this following error:
But first some Information:
Im using this SDK:
https://components.xamarin.com/view/facebookios
and creating my LoginButtin in my ViewController in ViewDidLoad like this:
loginButton = new LoginButton(new CGRect(48, 0, 218, 46))
{
LoginBehavior = LoginBehavior.Native,
ReadPermissions = readPermissions.ToArray()
};
View.AddSubview(button);
But in my Storyboard I get this error message:
Edit: my ViewController.cs
using System;
using System.Collections.Generic;
using UIKit;
namespace FacebookLogin
{
public partial class ViewController : UIViewController
{
List<string> readPermissions = new List<string> { "public_profile" };
LoginButton loginButton;
public ViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
loginButton = new LoginButton(new CGRect(48, 0, 218, 46))
{
LoginBehavior = LoginBehavior.Native,
ReadPermissions = readPermissions.ToArray()
};
View.AddSubview(button);
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
}
}
Make sure to follow the instructions found on the getting started page. I see an empty component in your solution explorer but just in case, make sure that you installed Xamarin.Facebook.iOS 4.27.1 with nugget. Of course, you also need to set up your facebook app, login, and configure the iOS portion (like setting the BundleID).
Don't create the button in the controller. What you can do is use the storyBoard designer to drop in a regular button. Then, in the properties window click on Class and it should open a dropdown menu. In the selection you should see FBSDKLoginButton, select that class. Give it a name like btnFacebook.
In the codebehind for your controller it will look like this:
string[] readPermissions = { "public_profile" };
public override void ViewDidLoad()
{
base.ViewDidLoad();
btn.LoginBehavior = LoginBehavior.Native;
btnFacebook.ReadPermissions = readPermissions;
// Handle actions once the user is logged in
btnFacebook.Completed += LoginView_Completed;
// Handle actions once the user is logged out
btnFacebook.LoggedOut += LoginView_LoggedOut;
}
private void LoginView_Completed(object sender, LoginButtonCompletedEventArgs e)
{
if (e.Error != null)
{
return;
}
if (e.Result.IsCancelled)
{
return;
}
}
private void LoginView_LoggedOut(object sender, EventArgs e)
{
}
For good measure, clean solution and recompile. The login button won't appear as a facebook login button in your designer but on runtime it will.
As for your error, I don't see anything in your designer so it's curious that it's giving you an error like that. Open the Document Outline (View -> Other Windows -> Document Outline) and see if there's any invisible garbage (elements that aren't being rendered) that has to be deleted.
First Of All! You're on the right Way To solve your issue
and Secondly, I would Suggest you that you have created a controller in the above image you need to create a View Controller File and then add a Login Screen Like a Username named Textbox Then a Password named Textbox and A login Button and then View Controller will automatically created once you save the view And Then Finally, You Save the view Controller By Adding the Following Code
partial void login_TouchUpInside(UIButton sender)
{
var auth = new OAuth2Authenticator(clientId: "YOUR_CLIENT_ID", scope: "", authorizeUrl: new Uri("https://m.facebook.com/dialog/oauth/"), redirectUrl: new Uri("http://www.facebook.com/connect/login_success.html"));
auth.Completed += Auth_Completed;
var ui = auth.GetUI();
PresentViewController(ui, true, null);
}
private async void Auth_Completed(object sender, AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var request = new OAuth2Request("POST", new Uri("YOUR Location Where You want to reach After Login"), null, e.Account);
//fb://profile/<id> For opening in Facebook App.
}
DismissViewController(true, null);
}
You See the Above Code And If you Want to Open the Facebook Link In Facebook Application Just Replace URL with
fb://profile/<id>

Limit user authorization to my Google domain

It should be possible to limit Google API OAuth2 requests to a specific google domain. It used to be possible by hacking on the end &hd=mydomain.com to the request. Using the new MVC auth stuff it seems no longer possible. Any ideas how?
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new AppGoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "***.apps.googleusercontent.com",
ClientSecret = "******"
},
Scopes = new[] { DriveService.Scope.Drive },
DataStore = new FileDataStore(HttpContext.Current.Server.MapPath("~/App_Data"), true) ,
});
public override string GetUserId(Controller controller)
{
// In this sample we use the session to store the user identifiers.
// That's not the best practice, because you should have a logic to identify
// a user. You might want to use "OpenID Connect".
// You can read more about the protocol in the following link:
// https://developers.google.com/accounts/docs/OAuth2Login.
var user = controller.Session["user"];
if (user == null)
{
user = Guid.NewGuid();
controller.Session["user"] = user;
}
return user.ToString();
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}
public class AppGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
{
public AppGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base(initializer) { }
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(String redirectUri)
{
var authorizeUri = new Uri(AuthorizationServerUrl).AddQuery("hd", "ourgoogledomain.com"); //is not in the request
var authUrl = new GoogleAuthorizationCodeRequestUrl(authorizeUri)
{
ClientId = ClientSecrets.ClientId,
Scope = string.Join(" ", Scopes),
RedirectUri = redirectUri,
//AccessType = "offline",
// ApprovalPrompt = "force"
};
return authUrl;
}
}
Passing a hd parameter is indeed the correct way to limit users to your domain. However, it is important that you verify that the user does actually belong to that hosted domain. I see in your answer that you figured out how to add this parameter back in to your request, so I will address the second part of this.
The issue is that the user can actually modify the requested URL in their client and remove the hd parameter! So while it's good to pass this parameter for the best UI for your users, you need to also verify that authenticated users do actually belong to that domain.
To see which hosted Google Apps for Work domain (if any) the user belongs to, you must include email in the list of scopes that you authorize. Then, do one of the following:
Option 1. Verify the ID Token.
When you exchange your code for an access token, the token endpoint will also return an ID Token in the id_token param (assuming you include an identity scope in your request such as email). If the user is part of a hosted domain, a hd claim will be present, you should check that it is present, and matches what you expect.
You can read more about ID tokens on Google's OpenID Connect docs (including some links to sample code and libraries to help you decode them). This tool can decode ID Tokens during testing.
Option 2. Call UserInfo
Once you have the OAuth Access Token, perform a GET request to https://www.googleapis.com/plus/v1/people/me/openIdConnect with the Access Token in the header. It will return a JSON dictionary of claims about the user. If the user is part of a hosted domain, a hd claim will be present, you should check that it is present, and matches what you expect.
Read more in the documentation for Google's UserInfo endpoint.
The main difference between Option 1 and Option 2 is that with the ID Token, you avoid another HTTP round-trip to the server making it faster, and less error-prone. You can try out both these options interactively using the OAuth2 Playground.
With the updated for .NET core package previous answers are no longer suitable. Fortunately in the new implementation there is a way to hook into authentication events to perform such task.
You will need a class that will handle 2 events - the one that fired before you go to Google and the one for when coming back. In first you limit which domain can be used to sign-in and in the second you ensure that the email with the right domain was in fact used for signin:
internal class GoogleAuthEvents : OAuthEvents
{
private string _domainName;
public GoogleAuthEvents(string domainName)
{
this._domainName = domainName?.ToLower() ?? throw new ArgumentNullException(nameof(domainName));
}
public override Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context)
{
return base.RedirectToAuthorizationEndpoint(new OAuthRedirectToAuthorizationContext(
context.HttpContext,
context.Options,
context.Properties,
$"{context.RedirectUri}&hd={_domainName}"));
}
public override Task TicketReceived(TicketReceivedContext context)
{
var emailClaim = context.Ticket.Principal.Claims.FirstOrDefault(
c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
if (emailClaim == null || !emailClaim.Value.ToLower().EndsWith(_domainName))
{
context.Response.StatusCode = 403; // or redirect somewhere
context.HandleResponse();
}
return base.TicketReceived(context);
}
}
and then you need to pass this "events handler" to the middleware via GoogleOptions class:
app.UseGoogleAuthentication(new GoogleOptions
{
Events = new GoogleAuthEvents(Configuration["Authentication:Google:LimitToDomain"])
})
#AMH, to do in simplest way you should create your own Google Provider, override method ApplyRedirect and append additional parameter like hd to address which will be using to redirect to a new google auth page:
public class GoogleAuthProvider : GoogleOAuth2AuthenticationProvider
{
public override void ApplyRedirect(GoogleOAuth2ApplyRedirectContext context)
{
var newRedirectUri = context.RedirectUri;
newRedirectUri += string.Format("&hd={0}", "your_domain.com");
context.Response.Redirect(newRedirectUri);
}
}
After that just link new provider to your options:
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = "your id",
ClientSecret = "your secret",
Provider = new GoogleAuthProvider(),
});
Having downloaded the source, I was able to see it is easy to subclass the request object, and add custom parameters:
public class GoogleDomainAuthorizationCodeRequestUrl : GoogleAuthorizationCodeRequestUrl
{
/// <summary>
/// Gets or sets the hosted domain.
/// When you want to limit authorizing users from a specific domain
/// </summary>
[Google.Apis.Util.RequestParameterAttribute("hd", Google.Apis.Util.RequestParameterType.Query)]
public string Hd { get; set; }
public GoogleDomainAuthorizationCodeRequestUrl(Uri authorizationServerUrl) : base(authorizationServerUrl)
{
}
}
public class AppGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
{
public AppGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base(initializer) { }
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(String redirectUri)
{
var authUrl = new GoogleDomainAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
{
Hd = "mydomain.com",
ClientId = ClientSecrets.ClientId,
Scope = string.Join(" ", Scopes),
RedirectUri = redirectUri
};
return authUrl;
}
}
I found this post when searching for a solution to specify the hosted domain with OpenID Connect integration to Google. I was able to get it working using the Google.Apis.Auth.AspNetCore package and the following code.
In Startup.cs
services.AddGoogleOpenIdConnect(options =>
{
options.ClientId = "*****";
options.ClientSecret = "*****";
options.SaveTokens = true;
options.EventsType = typeof(GoogleAuthenticationEvents);
});
services.AddTransient(provider => new GoogleAuthenticationEvents("example.com"));
Don't forget app.UseAuthentication(); in the Configure() method of Startup.cs.
Then the authentication events class
public class GoogleAuthenticationEvents : OpenIdConnectEvents
{
private readonly string _hostedDomain;
public GoogleAuthenticationEvents(string hostedDomain)
{
_hostedDomain = hostedDomain;
}
public override Task RedirectToIdentityProvider(RedirectContext context)
{
context.ProtocolMessage.Parameters.Add("hd", _hostedDomain);
return base.RedirectToIdentityProvider(context);
}
public override Task TicketReceived(TicketReceivedContext context)
{
var email = context.Principal.FindFirstValue(ClaimTypes.Email);
if (email == null || !email.ToLower().EndsWith(_hostedDomain))
{
context.Response.StatusCode = 403;
context.HandleResponse();
}
return base.TicketReceived(context);
}
}

how to use two AntiForgeryToken in a single page without using The deprecated 'Salt' property

How to use many #Html.AntiForgeryToken() in one page?
When I put it doesn't work on the remote host, only locally!
I tried to use different strings foreach forgery token
#Html.AntiForgeryToken("logoff_forgery") but when I add [ValidateAntiForgeryToken(Salt = "logoff_forgery")] in the controller, I get this following error
'System.Web.Mvc.ValidateAntiForgeryTokenAttribute.Salt'
'The 'Salt' property is deprecated.
To specify custom data to be embedded within the token,
use the static AntiForgeryConfig.AdditionalDataProvider property.'
D:\projects\codesan\app\CodeSan\CodeSan\Controllers\AccountController.cs
289 35 CodeSan
Does anyone know how to use the static AntiForgeryConfig.AdditionalDataProvider ? If yes please share it with me.
As it states in the description Salt property is deprecated.
Here is a simple implementation for IAntiForgeryAdditionalDataProvider
public class MyAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider
{
public string GetAdditionalData(HttpContextBase context)
{
return GenerateTokenAndSaveItToTheDB();
}
public bool ValidateAdditionalData(HttpContextBase context, string additionalData)
{
Guid token = Guid.TryParse(additionalData, out token) ? token : Guid.Empty;
if (token == Guid.Empty) return false;
return GetIfTokenIsFoundInTheDBAndNotExpired(token);
}
private string GenerateTokenAndSaveItToTheDB()
{
var newToken = Guid.NewGuid().ToString();
//save it to the db
return newToken;
}
}
And you simply register it in the Global.asax.cs
protected void Application_Start()
{
AntiForgeryConfig.AdditionalDataProvider = new MyAntiForgeryAdditionalDataProvider();
}

Orchard CMS ContentManager.New<>() Specified Cast Was Invalid

I am in the early stages of developing a new module.
Much of it is laid out in terms of the models etc. Also have the migrations all set up and my database now has the tables for my module.
I am encountering the following error when calling ContentManager.New<myPart> and would like some help please.
Error is this:
An unhandled exception has occurred and the request was terminated. Please refresh the page. If the error persists, go back
Specified cast is not valid.
System.InvalidCastException: Specified cast is not valid.
at Orchard.ContentManagement.ContentCreateExtensions.New[T]
(IContentManager manager, String contentType)
The chunk of code that fires the exception is this:
public static T New<T>(this IContentManager manager, string contentType) where T : class, IContent {
var contentItem = manager.New(contentType);
if (contentItem == null)
return null;
var part = contentItem.Get<T>();
if (part == null)
throw new InvalidCastException();
return part;
}
Here are the various parts to my module that are related to the operation i am struggling with:
ContentPart
public class GoogleMapsSettingsPart : ContentPart<GoogleMapsSettingsPartRecord>
{
public string ApiKey {
get { return Record.ApiKey; }
set { Record.ApiKey = value; }
}
}
ContentPartRecord
public class GoogleMapsSettingsPartRecord : ContentPartRecord
{
public virtual string ApiKey { get; set; }
}
Handler
public GoogleMapsSettingsPartHandler(IRepository<GoogleMapsSettingsPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
Migration for this table
// Settings Table
SchemaBuilder.CreateTable("GoogleMapsSettingsPartRecord", table => table
.ContentPartRecord()
.Column("ApiKey", DbType.String, c => c.WithLength(60))
);
Some of the code from the controller for this model etc
public AdminController(IContentManager contentManager, IShapeFactory shapeFactory, IServiceLocatorService serviceLocatorService, INotifier notifier)
{
_contentManager = contentManager;
_serviceLocatorService = serviceLocatorService;
_notifier = notifier;
Shape = shapeFactory;
T = NullLocalizer.Instance;
}
/// <summary>
/// Display Settings
/// </summary>
/// <returns></returns>
public ActionResult Settings()
{
var settings = _serviceLocatorService.GoogleMapsSettings;
var editor = CreateSettingsEditor(settings);
var model = _services.ContentManager.BuildEditor(settings);
return View((object)model);
}
Finally - the Services where my call throws this exception
private GoogleMapsSettingsPart _settings;
public GoogleMapsSettingsPart GoogleMapsSettings
{
get {
if (_settings == null)
{
_settings = _contentManager.Query<GoogleMapsSettingsPart, GoogleMapsSettingsPartRecord>().List().FirstOrDefault();
if (_settings == null)
{
_settings = _contentManager.New<GoogleMapsSettingsPart>("GoogleMapsSettings");
}
}
return _settings;
}
}
The actual line where the exception happens is _settings = _contentManager.New<GoogleMapsSettingsPart>("GoogleMapsSettings");
I have tried all sorts of stuff in place of "GoogleMapsSettings" though nothing is working.
I'm pretty sure at this point it's something simple, though it's avoiding me..My limited knowledge of Orchard is stumping me
Any help would be appreciated :)
The exception is thrown because your content type does not have the part you specified to get.
_contentManager.New<GoogleMapsSettingsPart>("GoogleMapsSettings");
This method call creates a new content item of type GoogleMapSettings and gets the content item as a GoogleMapsSettingsPart. However, it seems that GoogleMapSettings content type does not have a GoogleMapsSettingsPart. That's why the exception gets thrown here.
var part = contentItem.Get<T>();
if (part == null)
throw new InvalidCastException();
You must either attach the part dynamically to your content type or do it in a migration (or manually in the admin, but that's not a good idea). Your migration should look like this.
this.ContentDefinitionManager.AlterTypeDefinition("GoogleMapsSettings",
alt => alt
.WithPart("GoogleMapsSettingsPart");
Ok, so I fixed it...
My understanding of how Orchard works is still very much in the learning stages.
for this particular operation I didn't want to have a content type in the admin - though not sure why after adding the ContentType it still didn't work...
anyway, adding the lines below to my handler took care of the rest. I believe it's actually creating a temporary type so one isn't needed in the system.
public GoogleMapsSettingsPartHandler(IRepository<GoogleMapsSettingsPartRecord> repository)
{
Filters.Add(new ActivatingFilter<GoogleMapsSettingsPart>("GoogleMapsSettings"));
Filters.Add(StorageFilter.For(repository));
Filters.Add(new TemplateFilterForRecord<GoogleMapsSettingsPartRecord>("GoogleMapsSettings", "Parts/GoogleMapsSettings"));
}
I'v got the same error, but in my case it was everything ok with migration class.
The reason was unlucky merge, which deleted my driver class of my part.
Just look at this code of Activating method of ContentPartDriverCoordinator class. In my case there was no partInfo for my content part and resulted part became ContentPart, so casting throws an exception
var partInfos = _drivers.SelectMany(cpp => cpp.GetPartInfo()).ToList();
foreach (var typePartDefinition in contentTypeDefinition.Parts) {
var partName = typePartDefinition.PartDefinition.Name;
var partInfo = partInfos.FirstOrDefault(pi => pi.PartName == partName);
var part = partInfo != null
? partInfo.Factory(typePartDefinition)
: new ContentPart { TypePartDefinition = typePartDefinition };
context.Builder.Weld(part);
}

Resources