I have IDS4 and a Xamarin.Forms app all working fine except one little issue. Every single time the iOS app accesses the IDP server it first gives me this prompt:
"AppName" Wants to Use "" to Sign In
This allows the app and website to share information about you
What is causing this?
I have this error using IdentityModel.OidcClient2. Please see this link for the cause. This is the gist of it:
Cause
This is a system dialog that was added in iOS 11 to SFAuthenticationSession. It is triggered by this code in AppAuth:
SFAuthenticationSession* authenticationVC =
[[SFAuthenticationSession alloc] initWithURL:requestURL
callbackURLScheme:redirectScheme
completionHandler:^(NSURL * _Nullable callbackURL,
NSError * _Nullable error) {
There isn't a way to get rid of the dialog, except to not use SFAuthenticationSession which means you lose Single SignOn, which is worse.
I ended up using SFSafariViewController instead of SFAuthenticationSession by using the method mentioned by MLeech HERE
Solution
Which basically meant add these lines to your AppDelegate.cs
public override UIWindow Window
{
get;
set;
}
public static Action<string> CallbackHandler { get; set; }
public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
{
CallbackHandler(url.AbsoluteString);
CallbackHandler = null;
return true;
}
Then use this code for your SFAuthenticationSessionBrowser.cs
public class SFAuthenticationSessionBrowser : IBrowser
{
public Task<BrowserResult> InvokeAsync(BrowserOptions options)
{
var task = new TaskCompletionSource<BrowserResult>();
var safari = new SFSafariViewController(new NSUrl(options.StartUrl));
AppDelegate.CallbackHandler = async url =>
{
await safari.DismissViewControllerAsync(true);
task.SetResult(new BrowserResult()
{
Response = url
});
};
// https://forums.xamarin.com/discussion/24689/how-to-acces-the-current-view-uiviewcontroller-from-an-external-service
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
vc.PresentViewController(safari, true, null);
return task.Task;
}
}
Related
I have created a project using abpframwork. When running swagger, swagger receives the function in the application layer is a api. I don't want that. Can you guys tell me how to remove it in swagger
Code in Application Layer
public class UserService : AdminSSOAppService, ITransientDependency, IValidationEnabled, IUserService
{
IUserRepository _userRepository;
private readonly ILogger<UserService> _log;
public UserService(IUserRepository userRepository,
ILogger<UserService> log
)
{
_userRepository = userRepository;
_log = log;
}
public async Task<List<UserDto>> GetList()
{
var list = await _userRepository.GetListAsync();
return ObjectMapper.Map<List<User>, List<UserDto>>(list);
}
public async Task<UserDto> GetUserById(int Id)
{
var user = await _userRepository.GetAsync(c=>c.Id == Id);
return ObjectMapper.Map<User, UserDto>(user);
}
}
Code in HttpApi Layer
[Area(AdminSSORemoteServiceConsts.ModuleName)]
[RemoteService(Name = AdminSSORemoteServiceConsts.RemoteServiceName)]
[Route("api/user/user-profile")]
public class UserController : ControllerBase, IUserService
{
private readonly IUserService _userAppService;
public UserController(IUserService userAppService)
{
_userAppService = userAppService;
}
[HttpGet]
[Route("get-list-httpapi")]
public Task<List<UserDto>> GetList()
{
return _userAppService.GetList();
}
[HttpGet]
[Route("get-by-id-httpapi")]
public Task<UserDto> GetUserById(int Id)
{
return _userAppService.GetUserById(Id);
}
}
I can suggest a workaround as to enable only the APIs you need to appear on swagger (though the ones that don't appear anymore will still be available for consumption).
I would suggest you add a configuration part in your *.Http.Api project module inside your ConfigureSwaggerServices, like so:
context.Services.AddSwaggerGen(options =>
{
options.DocInclusionPredicate(
(_, apiDesc) =>
apiDesc
.CustomAttributes()
.OfType<IncludeInSwaggerDocAttribute>()
.Any());
});
And for the attribute, it would be very simple, like so:
[AttributeUsage(AttributeTargets.Class)]
public class IncludeInSwaggerDocAttribute : Attribute
{
}
This will let you achieve what you want, however I still recommend reading the doc carefully to be able to implement DDD.
I am using the new Standalone Blazor WASM Authentication flow with IdentityServer4. I would like to provide a message to the user that they were logged out due to inactivity. I already have this functioning with a low quality js alert() but I was wondering if I can make this work with a custom popup window or maybe a redirect parameter sent to identityserver to show them the alert on the identityserver login page.
I can't quite figure out a way to interrupt the immediate redirect that occurs after OnLogoutSucceeded. The js alert() pauses the redirect and works. Could I maybe modify the outgoing login redirect uri to give a parameter to IDS4?
<RemoteAuthenticatorView Action="#Action" OnLogOutSucceeded="LogoutSucceeded">
</RemoteAuthenticatorView>
#code{
[Parameter] public string Action { get; set; }
private async Task LogoutSucceeded()
{
await JsInterop.InvokeVoidAsync("alert", "You have been logged out due to inactivity.");
}
}
I figured it out:
//program.cs
builder.Services.AddOidcAuthentication<ApplicationAuthenticationState>(options =>
{
//options
});
//Authentication.razor
<RemoteAuthenticatorViewCore Action="#Action"
TAuthenticationState="ApplicationAuthenticationState"
OnLogOutSucceeded="LogoutSucceeded"
AuthenticationState="AuthState" />
[Parameter]
public string Action { get; set; }
public ApplicationAuthenticationState AuthState { get; set; } = new ApplicationAuthenticationState();
public bool Idled { get; set; }
protected override void OnInitialized()
{
if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut, Action))
{
var uri = NavManager.ToAbsoluteUri(NavManager.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("idle", out var param))
{
AuthState.Idle = !string.IsNullOrWhiteSpace(param);
}
}
}
private void LogoutSucceeded(ApplicationAuthenticationState state)
{
Idled = state.Idle;
if (Idled)
{
// save redirect for later
var returnUrl = state.ReturnUrl;
// cancel redirect
state.ReturnUrl = null;
// implement custom flow
}
}
I'm trying to create an app to open a local PDF file using web browser in WPF. However the file doesn't open properly, instead displays a grey blank screen. The code works perfectly fine when used to open a HTML file. Please help!
Code: webBrowser1.Navigate(#"file:///C:/Working/sample.pdf");
Note: I have adobe reader installed in my PC, if that is necessary. Is it?
WPF by default uses IE-based WebBrowser. In order to be able to view PDF-files, you must have a plugin installed into IE which can display PDF-files.
In addition to grey background, this is what can happen with a PC where IE doesn't have a PDF-plugin (Acrobat Reader etc) installed:
If you don't want to install plugins, one option to get around this issue is to use Windows 10 APIs to draw the PDF.
Other option is a 3rd party library, like CefSharp. Here's steps for using CefSharp:
First install Nuget CefSharp.WPF
Second, change XAML from the default WebBrowser to:
<wpf:ChromiumWebBrowser Loaded="ChromiumWebBrowser_Loaded" x:Name="Browser"></wpf:ChromiumWebBrowser>
Then create custom resolvers for CefSharp:
public class CustomProtocolSchemeHandler : ResourceHandler
{
public CustomProtocolSchemeHandler()
{
}
public override bool ProcessRequestAsync(IRequest request, ICallback callback)
{
return true;
}
}
public class CustomProtocolSchemeHandlerFactory : ISchemeHandlerFactory
{
public const string SchemeName = "customFileProtocol";
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
{
return new CustomProtocolSchemeHandler();
}
}
Almost lastly, register the resolvers in App.xaml.cs:
public partial class App : Application
{
protected override void OnLoadCompleted(NavigationEventArgs e)
{
var settings = new CefSettings();
settings.RegisterScheme(new CefCustomScheme
{
SchemeName = CustomProtocolSchemeHandlerFactory.SchemeName,
SchemeHandlerFactory = new CustomProtocolSchemeHandlerFactory(),
IsCSPBypassing = true
});
settings.LogSeverity = LogSeverity.Error;
Cef.Initialize(settings);
}
}
Now everything should work:
More information about using CefSharp: https://www.codeproject.com/Articles/881315/Display-HTML-in-WPF-and-CefSharp-Tutorial-Part
I'll probably add a few changes to #Mikael's code (In case something didn't work out for you)
public class CustomProtocolSchemeHandler : ResourceHandler
{
public CustomProtocolSchemeHandler()
{
}
public override CefSharp.CefReturnValue ProcessRequestAsync(IRequest request, ICallback callback)
{
return CefSharp.CefReturnValue.Continue;
}
}
public class CustomProtocolSchemeHandlerFactory : ISchemeHandlerFactory
{
public const string SchemeName = "customFileProtocol";
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
{
return new CustomProtocolSchemeHandler();
}
}
I've tested Geofence example by cn1 where it sets local notification. When the app is closed(get destroyed), it still gives notification. But I want to get location through GPS and run connectionRequest to save them in the server. I replaced the connectionRequest code instead of LocalNotification in following code but it doesnot work. What should I do to run the connectionRequest when the app is closed(not when it is minimized but destroyed) so that once the user installs and close (destroys) it, the app sent his/her location data in the server forever untill the app is uninstalled.
Geofence gf = new Geofence("test", loc, 100, 100000);
LocationManager.getLocationManager().addGeoFencing(GeofenceListenerImpl.class, gf);
Geofence with localNotification:
public class GeofenceListenerImpl implements GeofenceListener {
#Override
public void onExit(String id) {
}
#Override
public void onEntered(String id) {
if(Display.getInstance().isMinimized()) {
Display.getInstance().callSerially(() -> {
Dialog.show("Welcome", "Thanks for arriving", "OK", null);
});
} else {
LocalNotification ln = new LocalNotification();
ln.setId("LnMessage");
ln.setAlertTitle("Welcome");
ln.setAlertBody("Thanks for arriving!");
Display.getInstance().scheduleLocalNotification(ln, 10, LocalNotification.REPEAT_NONE);
}
}
}
Why the following doesnot work? (it only work when the app is running or is minimized but not when it is destroyed.)
public class GeofenceListenerImpl implements GeofenceListener {
#Override
public void onExit(String id) {
System.out.println("geofence onExit");
}
#Override
public void onEntered(String id) {
if(Display.getInstance().isMinimized()) {
Display.getInstance().callSerially(() -> {
System.out.println("geofence isMinimized");
});
} else {
System.out.println("geofence when app is closed");
//I want to run connectionRequest here but is not working
}
}
}
PS. I've used background fetch but it only works when the app is minimized.
Update1: Demo of how I used connectionRequest outside of minimized() method...
public class GeofenceListenerImpl implements GeofenceListener {
#Override
public void onExit(String id) {
System.out.println("geofence onExit");
}
#Override
public void onEntered(String id) {
if(Display.getInstance().isMinimized()) {
Display.getInstance().callSerially(() -> {
});
} else {
System.out.println("geofence when app is closed");
Connection c = new Connection();
c.liveTrackConnectionMethod("22" , "23");
}
}
}
Connection class
public class Connection {
ArrayList<Map<String, Object>> response;
public void liveTrackConnectionMethod(String lat, String lon) {
ConnectionRequest cr = new ConnectionRequest() {
#Override
protected void readResponse(InputStream input) throws IOException {
JSONParser jSONParser = new JSONParser();
Map parser = jSONParser.parseJSON(new InputStreamReader(input));
response = null;
}
};
cr.setPost(true);
cr.setUrl("http://url.com");
cr.addArgument("userid", Preferences.get(AllUrls.userIdPreference, null));
cr.addArgument("lat", lat + "");
cr.addArgument("long", lon + "");
cr.addRequestHeader("Accept", "application/json");
NetworkManager.getInstance().addToQueueAndWait(cr);
}
}
I think an app will always return false for isMinimized() when the app is closed or minimized (i.e. not currently running in the foreground) I may be wrong about this.
Try calling your connectionRequest script outside the isMinimized(). After all, you will want to keep track of user location whether they are using the app or not.
Your first solution with LocalNotification will show users a notification by calling the else part, rather than the Dialog when they're using the app, because isMinimized() will be false.
I want to define a SuggestBox, which behaves like the search bar in Google Maps: When you begin to type, real addresses, starting with the typed letters, appear.
I think, that I need to use the Geocoder.getLocations(String address, LocationCallback callback) method, but I have no idea how to connect this with the oracle, which is needed by the suggest box to produce its suggestions.
Can you please give me ideas how do I connect the getLocations Method with the SuggestOracle?
I solved this by implementing a subclass of SuggestBox, which has it's own SuggestOracle. The AddressOracle deals as a Wrapper for the Google Maps Service, for which the class Geocoderin the Google Maps API for GWT offers abstractions.
So here is my solution:
First we implement the Widget for a SuggestBox with Google Maps suggestions
public class GoogleMapsSuggestBox extends SuggestBox {
public GoogleMapsSuggestBox() {
super(new AddressOracle());
}
}
Then we implement the SuggestOracle, which wraps the Geocoder async method abstractions:
class AddressOracle extends SuggestOracle {
// this instance is needed, to call the getLocations-Service
private final Geocoder geocoder;
public AddressOracle() {
geocoder = new Geocoder();
}
#Override
public void requestSuggestions(final Request request,
final Callback callback) {
// this is the string, the user has typed so far
String addressQuery = request.getQuery();
// look up for suggestions, only if at least 2 letters have been typed
if (addressQuery.length() > 2) {
geocoder.getLocations(addressQuery, new LocationCallback() {
#Override
public void onFailure(int statusCode) {
// do nothing
}
#Override
public void onSuccess(JsArray<Placemark> places) {
// create an oracle response from the places, found by the
// getLocations-Service
Collection<Suggestion> result = new LinkedList<Suggestion>();
for (int i = 0; i < places.length(); i++) {
String address = places.get(i).getAddress();
AddressSuggestion newSuggestion = new AddressSuggestion(
address);
result.add(newSuggestion);
}
Response response = new Response(result);
callback.onSuggestionsReady(request, response);
}
});
} else {
Response response = new Response(
Collections.<Suggestion> emptyList());
callback.onSuggestionsReady(request, response);
}
}
}
And this is a special class for the oracle suggestions, which just represent a String with the delivered address.
class AddressSuggestion implements SuggestOracle.Suggestion, Serializable {
private static final long serialVersionUID = 1L;
String address;
public AddressSuggestion(String address) {
this.address = address;
}
#Override
public String getDisplayString() {
return this.address;
}
#Override
public String getReplacementString() {
return this.address;
}
}
Now you can bind the new widget into your web page by writing the following line in the onModuleLoad()-method of your EntryPoint-class:
RootPanel.get("hm-map").add(new GoogleMapsSuggestBox());