I wish to send a request to a Website (server) to update a potential scoreboard which everyone who has the application can see. This website isn't of course restricted to just the application users - but it can be accessible to anyone.
Is it possible to call a WCF from a Windows Phone app for example where the WCF can then update the database. So whenever someone goes on the website, the updated changes will be seen.
Is this at all possible? And if it is, would this be the most sensible/optimised way of doing things?
I did this using a BsckgroundWorker in my ViewModel to prevent hanging the UI. This way, you can "set it and forget it". Something like this:
private void UpdateScoreboard()
{
var scoreboardWorker = new BackgroundWorker();
scoreboardWorker.DoWork += (s,dwe)=>{
//call some WCF service compleate async
};
scoreboardWorker.RunWorkerCompleted += (s,rwe)=>{
// check whether rwe.Error is not null, indicating an exception. Show an alert maybe
};
scoreboardWorker.RunWorkerAsync();
}
Related
I am fairly new to webapps programming, so I thought of asking here.
I am implementing the SAML2 protocol in an open source app (namely OFBiz) but I am encountering a problem related to session loss after the protocol made its course.
I am following these steps to implement the protocol. Suppose ofbizwebsite.com is the URL of the site.
Installed a custom plugin named SAMLIntegration which exposes the ACS page and the logic for login. To my understanding, a plugin (gradle) is like an indipendent java project, which translates to a new set of resources for the application (the plugin enables, for example, to visit ofbizwebsite.com/SAMLIntegration and setup some resources).
Exposed the ACS page to ofbizwebsite.com/SAMLIntegration/control/acs, as well as metadata ofbizwebsite.com/SAMLIntegration/control/metadata.jsp
Created the logic for login. Basically, an entity called UserLogin is saved in the session and recovered by a "checker" to understand if an user is logged in. Suppose that this checker is a HTTP WebEvent handler which can be called by any resource requiring authentication.
Now, the problem. If redirect the user to a resource on SAMLIntegration (for example ofbizwebsite.com/SAMLIntegration/control/aview or any ofbizwebsite.com/SAMLIntegration/control/* by calling response.sendRedirect("aview")) check works and login is preserved. Visiting any resource (for example ofbizwebsite.com/aplugin/control/anotherview) by navigating the application does not preserve the session.
OFBiz use internally a mechanism for preserving the userLogin between webapps, by creating an HashMap between and UUID and a UserLogin object. The UUID is passed between two different resources, appending this key to each path (so ofbizwebsite.com/aplugin/control/anotherview?externalKey=THEEFFECTIVEUUID)
To my understanding, changing from ofbizwebsite.com/SAMLIntegration/control/* to ofbizwebsite.com/aplugin/control/* determine a session loss. So, my idea was to replace the UUID mechanism with SAML2. However, I do not know how to solve this problem.
In particular, I would like to execute a SAML request each time the checker function is executed. If I can't find the user in the session, a SAML request is fired. However, my problem is HOW to manage the response. Normally, I would redirect it to the acs ofbizwebsite.com/SAMLIntegration/control/acs. Doing so, however, does not allow me to handle the response in the checker function, as the control is passed to another servlet by an external request (the SAML response fired by the IdP). Should I provide a different acs for each different path? (so one for SAMLIntegration and one for aplugin?) And, even if this was the case, how can I return the control to the checker function which has invoked the SAML request?
Here you go for installing the Shibboleth HTTPD module: https://pad.nereide.fr/SAMLWithShibboleth
You also need this method somewhere in OFBiz (I recommend LoginWorker.java, but you can put it where you want). It allows to use the externalAuthId of the userLogin for authentication, with the uid returned by the sso:
public static String checkShibbolethRequestRemoteUserLogin(HttpServletRequest request, HttpServletResponse response) {
LocalDispatcher dispatcher = (LocalDispatcher) request.getAttribute("dispatcher");
Delegator delegator = dispatcher.getDelegator();
// make sure the user isn't already logged in
if (!LoginWorker.isUserLoggedIn(request)) {
String remoteUserId = (String) request.getAttribute("uid"); // This is the one which works, uid at Idp, remoteUserId here
if (UtilValidate.isNotEmpty(remoteUserId)) {
//we resolve if the user exist with externalAuthId
String userLoginId = null;
GenericValue userLogin;
try {
List<GenericValue> userLogins = delegator.findList("UserLogin",
EntityCondition.makeConditionMap("externalAuthId", remoteUserId, "enabled", "Y"),
null, null, null, true);
userLogin = userLogins.size() == 1 ? userLogins.get(0) : null;
} catch (GenericEntityException e) {
Debug.logError(e, module);
return "error";
}
if (userLogin != null) {
userLoginId = userLogin.getString("userLoginId");
}
//now try to log the user found
return LoginWorker.loginUserWithUserLoginId(request, response, userLoginId);
}
}
return "success";
}
You also need to have this method as an OFBiz preprocessor in the webapp controllers. I suggest to have a look at common-controller.xml.
Finally you need the configs that redirect to the SSO page if no session. That should do the job, at least it works for them...
Finally I recommend https://www.varonis.com/blog/what-is-saml in case of need.
I have a standalone Google Apps Script deployed as a web app. The app is executed as me, because I want it to access files stored on my Drive, and because I want it to generate Google Sheets files that have some ranges protected from the user that are still editable by the script. However, I want these files to be segregated into folders, and each folder is assigned to a user, so I need to know who the user is each time the app runs.
Session.getActiveUser().getEmail() doesn't work since the web app is deployed as me and not as the user. My other thought was to make the app "available to everyone, even anonymous" (right now it's just "available to everyone") to skip Google's login screen, and use some kind of third-party authentication service or script. Building my own seems like overkill because this seems like it should already exist, but so far my research has only turned up things like Auth0 which seem incompatible with my simple Google Apps Script-based app, or else I'm too inexperienced to figure out how to use them.
Does anyone have a suggestion for how to authenticate users for this kind of web app? Preferably something that comes with a beginner-friendly tutorial or documentation? Or, is there another way for me to find out who's running the app while still executing it as myself?
I am so new to this I'm not even sure I'm asking this question in the right way, so suggested edits are taken gratefully.
I can think of two ways you might approach this where the Web App is deployed to execute as the user accessing it:
Scenario A: Create a service-account to access files stored on your Drive and to generate google sheets.
Scenario B: Create a separate Apps Script project deployed as an API Executable and call its functions from the main Web App.
These methods are viable but there are a number of pros and cons to each.
Both require OAuth2 authentication, but that bit is fairly easy to handle thanks to Eric Koleda's OAuth2 library.
Also, in both scenarios, you'll need to bind/link your main Apps Script project to a GCP project and enable the appropriate services, in your case Google Sheets and Google Drive APIs (see documentation for more details).
For Scenario A, the service account must be created under the same GCP project. For Scenario B, the secondary Apps Script project for the API executable must also be bound to the same GCP project.
Issues specific to Scenario A
You'll need to share the files and folders you want to access/modify (and/or create content in) with the service account. The service account has it own email address and you can share google drive files/folders with it as you would with any other gmail account.
For newly created content, permissions could be an issue, but thankfully files created under a folder inherit that folder's permissions so you should be good on that front.
However, you'll have to use the REST APIs for Drive and Sheets services directly; calling them via UrlFetch along with the access token (generated using the OAuth2 library) for the Service Account.
Issues specific to Scenario B
You'll need to setup a separate Apps Script project and build out a public API (collection of non-private functions) that can be called by a 3rd party.
Once the script is bound to the same GCP project as the main Web App, you'll need to generate extra OAuth2 credentials from GCP console under the IAM (Identity Access Management) panel.
You'll use the Client ID and Client Secret, to generate a refresh token specific to your account (using the OAuth2 library). Then you'll use this refresh token in your main Web App to generate the requisite access token for the API executable (also using the OAuth2 library). As in the previous scenario, you'll need to use UrlFetch to invoke the methods on the API Executable using the generated access token.
One thing to note, you cannot use triggers within the API executable code as they are not allowed.
Obviously, I've glossed over a lot of the details but that should be enough to get you started.
Best of luck.
Now that I've implemented TheAddonDepot's suggested Scenario B successfully, I wanted to share a few details that might help other newbies.
Here's what the code in my web app project looks like:
function doGet(e) {
// Use user email to identify user folder and pass to var data
var userEmail = Session.getActiveUser().getEmail();
// Check user email against database to fetch user folder name and level of access
var userData = executeAsMe('getUserData', [userEmail]);
console.log(userData);
var appsScriptService = getAppsScriptService();
if (!appsScriptService.hasAccess()) { // This block should only run once, when I authenticate as myself to create the refresh token.
var authorizationUrl = appsScriptService.getAuthorizationUrl();
var htmlOutput = HtmlService.createHtmlOutput('Authorize.');
htmlOutput.setTitle('FMID Authentication');
return htmlOutput;
} else {
var htmlOutput = HtmlService.createHtmlOutputFromFile('Index');
htmlOutput.setTitle('Web App Page Title');
if (userData == 'user not found') {
var data = { "userEmail": userEmail, "userFolder": null };
} else {
var data = { "userEmail": userData[0], "userFolder": userData[1] };
}
return appendDataToHtmlOutput(data, htmlOutput);
}
}
function appendDataToHtmlOutput(data, htmlOutput, idData) { // Passes data from Google Apps Script to HTML via a hidden div with id=idData
if (!idData)
idData = "mydata_htmlservice";
// data is encoded after stringifying to guarantee a safe string that will never conflict with the html
var strAppend = "<div id='" + idData + "' style='display:none;'>" + Utilities.base64Encode(JSON.stringify(data)) + "</div>";
return htmlOutput.append(strAppend);
}
function getAppsScriptService() { // Used to generate script OAuth access token for API call
// See https://github.com/gsuitedevs/apps-script-oauth2 for documentation
// The OAuth2Service class contains the configuration information for a given OAuth2 provider, including its endpoints, client IDs and secrets, etc.
// This information is not persisted to any data store, so you'll need to create this object each time you want to use it.
// Create a new service with the given name. The name will be used when persisting the authorized token, so ensure it is unique within the scope
// of the property store.
return OAuth2.createService('appsScript')
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId('[client ID]')
.setClientSecret('[client secret]')
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Enable caching to avoid exhausting PropertiesService quotas
.setCache(CacheService.getScriptCache())
// Set the scopes to request (space-separated for Google services).
.setScope('https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/spreadsheets')
// Requests offline access.
.setParam('access_type', 'offline')
// Consent prompt is required to ensure a refresh token is always
// returned when requesting offline access.
.setParam('prompt', 'consent');
}
function authCallback(request) { // This should only run once, when I authenticate as WF Analyst to create the refresh token.
var appsScriptService = getAppsScriptService();
var isAuthorized = appsScriptService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab.');
}
}
function executeAsMe(functionName, paramsArray) {
try {
console.log('Using Apps Script API to call function ' + functionName.toString() + ' with parameter(s) ' + paramsArray.toString());
var url = '[API URL]';
var payload = JSON.stringify({"function": functionName, "parameters": paramsArray, "devMode": true})
var params = {method:"POST",
headers: {Authorization: 'Bearer ' + getAppsScriptService().getAccessToken()},
payload:payload,
contentType:"application/json",
muteHttpExceptions:true};
var results = UrlFetchApp.fetch(url, params);
var jsonResponse = JSON.parse(results).response;
if (jsonResponse == undefined) {
var jsonResults = undefined;
} else {
var jsonResults = jsonResponse.result;
}
return jsonResults;
} catch(error) {
console.log('error = ' + error);
if (error.toString().indexOf('Timeout') > 0) {
console.log('Throwing new error');
throw new Error('timeout');
} else {
throw new Error('unknown');
}
} finally {
}
}
I generated the OAuth2 credentials at https://console.cloud.google.com/ under APIs & Services > Credentials > Create Credentials > OAuth Client ID, selecting "Web application". I had to add 'https://script.google.com/macros/d/[some long ID]/usercallback' as an authorized redirect URI, but I apologize as I did this two weeks ago and can't remember how I figured out what to use there :/ Anyway, this is where you get the client ID and client secret used in function getAppsScriptService() to generate the access token.
The other main heads up I wanted to leave here for others is that while Google Apps Scripts can run for 6 minutes before timing out, URLFetchApp.fetch() has a 60s timeout, which is a problem when using it to call a script via the API that takes more than 60s to execute. The Apps Script you call will still finish successfully in the background, so you just have to figure out how to handle your timeout error and call a follow-up function to get whatever the original function should have returned. I'm not sure if that makes sense, but here's the question I asked (and answered) on that issue.
We're building a web application using the Skype Web SDK (https://msdn.microsoft.com/en-us/skype/websdk/docs/skypewebsdk). We use both the audio and the IM capability to get connected to other parties.
Currently we're facing the following problem: If our application is in a conversation with another party (e. g. with a Skype for Business desktop client) and the user leaves or reloads the page, the other party doesn't get notified about the left user.
For audio conversations the result is the following: The other party is still in the call and the only indication of the leaving is that the other party can't hear anything.
For IM conversations the result is the following: If the other party sends an IM in this conversation it gets the notification that the message couldn't be delivered.
We've tried to leave the conversation before the unload of the page using the onbeforeunload event. The callback is executed both in IE 11 and Chrome, but only in Chrome the user actually leaves the conversation (tested with IM conversation since audio/video is not supported in Chrome).
window.onbeforeunload = function() {
// conversation is the conversation we're currently in
conversation.leave().then(function () {
client.conversationsManager.conversations.remove(conversation);
});
};
Since we rely on the audio capability we're not able to simply switch to Chrome only. Is there any way to ensure that the conversations are cleaned up on page reload/leave in Internet Explorer?
The problem with what you are trying to do is that the (on)beforeunload event does not wait around for asynchronous methods to complete so you are not guaranteed that leave() will execute nor the inner action to remove the conversation from the conversationsManager. I would suggest an approach similar to the following question - onbeforeunload confirmation screen customization or beforeunload
What you want to do is put the user into a state where the need to interact with a dialog which may (but also not guaranteed) give enough cycles to leave the conversation. In your case it might look something like the following:
window.onbeforeunload = function(e) {
// track if a conversation is live
if (_inActiveConversation) {
// conversation is the conversation we're currently in
conversation.leave().then(function () {
client.conversationsManager.conversations.remove(conversation);
});
var msg = 'Cleaning up active conversations...';
e.returnValue = msg;
return msg;
}
};
What you should also understand is that eventually the server side will remove that user from the conversation because the application is no longer processing incoming events. So the above is a best effort to clean up resources that will eventually be reclaimed.
You could also try to issuing a signOut request on the signInManager as that should allow the server to clean up resources related to that application on the server side.
window.onbeforeunload = function() {
...
_client.signInManager.signOut();
...
};
I have two websites, one client website and a pricing WEBAPI website. I have a performance issue with the pricing website as it often suspends itself due to low usage and on the first call takes time to initialize. If I repeat the request immediately after that, it is very quick.
I know that it will be used on certain pages of the client website, I therefore wish to call it when that page loads so its ready when the users valid request comes in seconds later. Please note the pricing WEBAPI site is not available from the client, only the client website can access it on the server side.
I don't know the best approach to this, I don't wish to impact the performance of the client website. I have considered an 1px x 1px iFrame calling a page but concerned it will block the page. Is an Ajax call more appropriate, but how to I call something on the client website to in turn call the webservice? Is there a better approach I haven't considered?
Known issue on shared hosting environments, a workaround is fine but I would suggest upgrading your server. My hosting has a DotNetNuke option, which essentially means it will reserve memory on the server and don't recycle the app pool due inactivity. Compared to VPS this is cheaper.
If it is not shared hosting, these IIS settings could help you.
Anyway, back to your workaround:
You say the client cannot access the webapi, only the back-end of your website can. Seems weird because an webapi exposes REST GET,POST methods. Either way you could do an async call to your webapi on server side that does not wait for a response or do a javascript call to your API.
Assuming your website is also ASP.NET:
public static async Task StartupWebapi()
{
string requestUrl = "http://yourwebapi.com/api/startup";
using (var client = new HttpClient())
{
//client.Timeout = new TimeSpan(0, 0, 20); timeout if needed
try
{
HttpResponseMessage response = await client.GetAsync(requestUrl);
if (response.IsSuccessStatusCode)
{
resultString = await response.Content.ReadAsStringAsync();
}
}
}
}
Then, somewhere in your code, that will be called at least when your client website starts.
HostingEnvironment.QueueBackgroundWorkItem(ct => SomeClass.StartupWebapi());
Or in javascript, which is executed asynchronously.
$.ajax({
url: "http://yourwebapi.com/api/startup",
type: "GET",
success: function (response) {
},
error: function (response) {
}
});
See this question for some other workarounds.
I'm working on implementing the ForgotPassword functionality ie in the AccountController using ASP.NET Identity as in the standard VS 2015 project template.
The problem I'm trying to solve is that when the password reset email is sent, there is a noticeable delay in the page response. If the password recovery attempt does not find an existing account then no email is sent so there is a faster response. So I think this noticeable delay can be used for account enumeration, that is, a hacker could determine that an account exists based on the response time of the forgot password page.
So I want to eliminate this difference in page response time so that there is no way to detect if an account was found.
In the past I've queued potentially slow tasks like sending an email onto a background thread using code like this:
ThreadPool.QueueUserWorkItem(new WaitCallback(AccountNotification.SendPasswordResetLink),
notificationInfo);
But ThreadPool.QueueUserWorkItem does not exist in .NET Core, so I'm in need of some alternative.
I suppose one idea is to introduce an artificial delay in the case where no account is found with Thread.Sleep, but I'd rather find a way to send the email without blocking the UI.
UPDATE: To clarify the problem I'm posting the actual code:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await userManager.FindByNameAsync(model.Email);
if (user == null || !(await userManager.IsEmailConfirmedAsync(user)))
{
// Don't reveal that the user does not exist or is not confirmed
return View("ForgotPasswordConfirmation");
}
var code = await userManager.GeneratePasswordResetTokenAsync(user);
var resetUrl = Url.Action("ResetPassword", "Account",
new { userId = user.Id, code = code },
protocol: HttpContext.Request.Scheme);
//there is a noticeable delay in the UI here because we are awaiting
await emailSender.SendPasswordResetEmailAsync(
userManager.Site,
model.Email,
"Reset Password",
resetUrl);
return View("ForgotPasswordConfirmation");
}
// If we got this far, something failed, redisplay form
return View(model);
}
Is there a good way to handle this using other built in framework functionality?
Just don't await the task. That's then mostly-equivalent to running all of that code on the thread-pool to start with, assuming it doesn't internally await anything without calling ConfigureAwait(false). (You'll want to check that, if it's your code.)
You might want to add the task to some set of tasks which should be awaited before the server shuts down, assuming there's some appropriate notion of "requested shutdown" in ASP.NET. That's worth looking into, and would stop the notification from being lost due to unfortunate timing of the server being shut down immediately after sending the response but before sending the notification. It wouldn't help in the case where there are problems in sending the notification though, e.g. your mail server is down. At that point, the user has been told that the email is on its way, before you can really guarantee that... just something to think about.