Showing posts with label Javascript. Show all posts
Showing posts with label Javascript. Show all posts

Thursday, 9 June 2022

Integrating Google Cloud reCAPTCHA Enterprise in PHP

Google Cloud reCAPTCHA Enterprise is the successor (?) to reCAPTCHA v3. 

The code snippets Google supplies for integrating reCATPCHA Enterprise in PHP are frustratingly uncohesive and neither a working demo or complete code sample exists (as far as I can find at the time of writing). 

Moreover, the code snippets that are provided were incomplete for my implementation and the number of moving parts (Cloud projects, keys and credentials) is nothing short of overwhelming. 

Given the above, I struggled for some time just get everything working--which is where I'm up to now. So a disclaimer for those of you readers who are proficient PHP developers and who possess a deeper understanding of the Google Cloud Platform, please use this as a starting point only. There may be a better (more refined) approach. 

In terms of the official documentation and the code snippets I refer to above and will discuss in this post, start here: https://cloud.google.com/recaptcha-enterprise/docs/choose-key-type

I should also note I'm working in a basic web hosting environment with PHP installed. It's not a Google environment--think more budget web host ;)

Setup

Before you can get started, you'll need to login to the Google Cloud Platform, create a new project if you don't have a suitable already, and create a reCAPTCHA Enterprise key. Save the key somewhere and take note of your Cloud project ID while you're there as we'll need that later. You'll find your project ID on the relevant dashboard card. 

Incidentally, you can follow the link to reCAPTCHA Enterprise in the documentation to get to where you need to be in the Google Cloud Platform (or just navigate to Menu > Security > reCAPTCHA Enterprise). 

Once that's done, you may want to hop into Menu > APIs and services. If the reCAPTCHA Enterprise API isn't already enabled, find it in the library and enable it. While you're here, you'll also want to create credentials. This is where things are still a bit blurry for me but I created both an API key and a service account. The API key isn't used in my final code and it's possibly not necessary. I'm just used to API keys, I guess! 

The service account basically receives a name and ID and is then assigned the reCAPTCHA Agent role (locate reCAPTCAHA Enterprise in the list of products and services and then select the reCAPTCHA Agent role). 

Once the service account exists, I chose to create a key ("Manage keys"). Google will warn you about the risks of downloading service account keys and suggests using Workload Identity Federation--I don't yet know what this is so do you own homework here. I created a new key (ADD KEY > Create new key) as a .json file--you'll be prompted to download/save this file and I'd suggest storing a copy in a safe place as I have yet to find a way to download this same file again in future. 

Code

With that setup done, we now move into the code. The code runs clients side and server side and is comprised of your HTML page (including a form), a couple of scripts in the HEAD tag and some attributes on the form button. And of course the PHP code. 

Be sure to add your reCAPTCHA Enterprise key to both the script tag in the head and the data-sitekey attribute on your button. It's also required in the call to create_assessment() in the PHP code. 

I'll point out that, in my case, I want to trigger a score-based reCAPTCHA assessment when a user submits a form--no sooner and not unecessarily. I'm therefore using the relevant Google examples here, combined into a single, cohesive file. 

The HTML and script source is taken from this page: https://cloud.google.com/recaptcha-enterprise/docs/instrument-web-pages 


Note the PHP code is slightly modified to include the putenv call, which references the JSON key file we created earlier. You'll also want to drop in your Google Cloud Platform project ID in the call to create_assessment(). 

<html>
<head>
	<script src="https://www.google.com/recaptcha/enterprise.js?render=your_recaptcha_key"></script>
	<script>
	   function onSubmit(token) {
		 document.getElementById("demo-form").submit();
	   }
	</script>
</head>
<body>
<form id="demo-form" method="post">
<input type="text" />
<button class="g-recaptcha"
data-sitekey="your_recaptcha_key"
data-callback='onSubmit'
data-action='submit'>Submit</button>
</form>
</body>
</html>

<?php

require 'google-re/vendor/autoload.php';

use Google\Cloud\RecaptchaEnterprise\V1\RecaptchaEnterpriseServiceClient;
use Google\Cloud\RecaptchaEnterprise\V1\Event;
use Google\Cloud\RecaptchaEnterprise\V1\Assessment;
use Google\Cloud\RecaptchaEnterprise\V1\TokenProperties\InvalidReason;

/**
* Create an assessment to analyze the risk of a UI action.
* @param string $siteKey The key ID for the reCAPTCHA key (See https://cloud.google.com/recaptcha-enterprise/docs/create-key)
* @param string $token The user's response token for which you want to receive a reCAPTCHA score. (See https://cloud.google.com/recaptcha-enterprise/docs/create-assessment#retrieve_token)
* @param string $project Your Google Cloud project ID
*/
function create_assessment(
  string $siteKey,
  string $token,
  string $project
): void {
	
  // *** I added this line ***
  putenv('GOOGLE_APPLICATION_CREDENTIALS=your-service-account-key-file.json');
    
  // TODO: To avoid memory issues, move this client generation outside
  // of this example, and cache it (recommended) or call client.close()
  // before exiting this method.
  $client = new RecaptchaEnterpriseServiceClient();

  $projectName = $client->projectName($project);

  $event = (new Event())
	  ->setSiteKey($siteKey)
	  ->setToken($token);

  $assessment = (new Assessment())
	  ->setEvent($event);

  try {
	  $response = $client->createAssessment(
		  $projectName,
		  $assessment
	  );

	  // You can use the score only if the assessment is valid,
	  // In case of failures like re-submitting the same token, getValid() will return false
	  if ($response->getTokenProperties()->getValid() == false) {
		  printf('The CreateAssessment() call failed because the token was invalid for the following reason: ');
		  printf(InvalidReason::name($response->getTokenProperties()->getInvalidReason()));
	  } else {
		  printf('The score for the protection action is:');
		  printf($response->getRiskAnalysis()->getScore());

		  // Optional: You can use the following methods to get more data about the token
		  // Action name provided at token generation.
		  // printf($response->getTokenProperties()->getAction() . PHP_EOL);
		  // The timestamp corresponding to the generation of the token.
		  // printf($response->getTokenProperties()->getCreateTime()->getSeconds() . PHP_EOL);
		  // The hostname of the page on which the token was generated.
		  // printf($response->getTokenProperties()->getHostname() . PHP_EOL);
	  }
  } catch (exception $e) {
	  printf('CreateAssessment() call failed with the following error: ');
	  printf($e);
  }
}

   create_assessment(
      'YOUR_RECAPTCHA_SITE_KEY',
      $_POST['g-recaptcha-response'], // Safety first! Do you trust this code?
      'YOUR_GOOGLE_CLOUD_PROJECT_ID'
    );
?>

Drop all of the above in a file named whatever.php and upload it to your web server. Before any of this will run on your server, you'll need to attend to some dependencies. 

Upload your .json key file alongside your .php file (or put it somewhere else on the server and amend the putenv path in the PHP code). This file may warrant additional protections. 

With that sorted, you need to fetch all of the Google Cloud files the PHP above depends on. You'll likely want to do this using Composer (which needs to be installed on your desktop) and you can then run this command in a command window: 

composer require google/cloud-recaptcha-enterprise

I'll note I'm only fetching the reCAPTCHA Enterprise bits here--not the entire Google Cloud file set. You do you. 

I saved all of the Google files in a directory named google-re, which you'll see referenced in the first line our PHP. Adjust as required. 

A final note for the FileZilla users out there: when uploading the Google dependencies in particular, you may encounter a horrible runtime exception ("Fail to push limit") if you don't configure the FileZilla transfer type as Binary (Transfer > Transfer type > Binary). Refer to the answer to this question for more information: https://groups.google.com/g/protobuf/c/8_S93nJWxUE?pli=1

Run It

And now you should be able to request your page, submit the button. All being well, you'll encounter no exceptions and receive a nice meessage like "The score for the protection action is:0.89999997615814". Use that and other, related assessment information to act accordingly.  

I hope that helps someone!


Wednesday, 13 October 2010

SharePoint and Chrome - Better Together

I've been using Google's Chrome browser since its first release in 2008; I've loved nearly every second of the experience. Who would've thought there was room left to innovate in the browser space? Chrome's omnibar and rapid-fire JavaScript rendering, among other tweaks, are simply light years ahead of the competition.

While I normally rely on IE for my MOSS/SharePoint editing interactions, as of late I'm making the switch to Chrome in that space as well. What I've found to date has blown my mind.

Yes the MOSS 2007 UI degrades somewhat but it's still very useable. More importantly, Chrome drastically reduces the time it takes to accomplish basic tasks like modifying page content or viewing list data. I'm not saying these are normally slow in SharePoint but they can be in the www.westernaustralia.com environment (it's an ageing site with a lot of content and a lot of customisations); some pages in particular nearly grind to a halt in IE8 with the corresponding process consuming upwards of 1GB of memory the more I interact with the page.

Chrome "fixes" many of these slowdowns I'd previously attributed to the SharePoint environment and gives me all the Chrome goodness I've come to love over the last two years. It almost makes the SharePoint editing experience pleasurable!

If you found this post helpful, please support my advertisers.

Monday, 13 September 2010

How to pass JSON arrays and other data types to an ASMX web service

Ah interoperability… great fun, great fun.

So jQuery is your new best friend and, along with JSON, there's nuthin' you can't do. The server side stuff is still there in the background and you've got some old school ASP.NET (.asmx) web services hanging around but DOM elements are otherwise flying all over the place, postbacks are just so passé, and even the marketing girls are mildly impressed at your skillz. You're branching out, shifting code and complexity from the server to the browser, and it's time to do some heavier data shunting. Here a few things to know about passing JSON data to an ASMX web service that may help you on your way…

JSON.stringify

Know it, use it, love it. It's part of the JSON2 library and you need it if you don't have it already. Use it to prepare (aka properly encode) your JSON data before sending it off to the big mean ol' web server:

data: {"days": JSON.stringify(["Mon", "Tues"])}

That will encode as &days=["Monday","Tuesday"]

Yeah, I know, it's another file to download but the guy who wrote JSON also wrote this and it can be merged and minified. I've tried writing my own mini-version as a function and while this works for simple strings, save yourself some time when it comes to arrays and the like and just use this sucker.

Arrays

Arrays seem trickier than they should… maybe I'm just a dumb guy—probably. Anyway, you can pass a JSON array to an .asmx web service without much work at all.

The client-side call listed above is everything you need to do from that end. On the server side, create yourself a new web service method with a List<string> parameter:

[WebMethod]
[ScriptMethod (UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
public string ConsumeArray (List<string> days)
{…}

That's all there is to it. If you're not passing in strings, declare the List<> parameter with a type of object or something else. You can use .NET arrays in the web method signature as well if you really want (need) to.

Integers

When in doubt, stringify:

data: { "i": JSON.stringify(2) }

An int parameter on the web service end will handle this graciously.

Booleans

The good ol' boolean—a simple concept computer science has managed to bastardise like no other…

When in doubt, stringify:

data: { "b": jsonpEncode("true") }

Like the int parameter, a bool in your web method signature will take care of this.

A brief note: JSON, or rather jQuery's parseJSON function, is a particular beast and doesn't seem to know about anything other than the lower case true and false strings. If, for any reason, you ToString a bool in your .NET web service and try to pass it back, parseJSON will fail. If you forget to brush your teeth in the morning, parseJSON will fail.

Dates

Sorry, on my todo list ;-)

Tools

When working through this stuff, it pays to have Fiddler open to inspect the requests you're sending through and any error messages you're getting back. I find Fiddler sometimes breaks this stuff so try turning off the capture if you're getting weird errors; optionally, revert to Firebug (Firefox only, of course).

Fully decoding the data you sniff from a JSONP request passed along in the query string will require some additional tooling; in short, you'll want to decode the value using a free online tool like Opinionated Geek's URL decoder.

If you found this post helpful, please support my advertisers.

Friday, 2 July 2010

.ajax beforeSend Doesn't Fire

I absolutely adore jQuery but there are some things that are just plain hard to do with the current version. One of those is cross-domain (JSONP) requests. No only is the setup convoluted but the requirements are exacting to arrive at a working solution for something that should be (?) reasonably simple. But I'll save the details for another time—for now, just know I'm building with jQuery and using JSONP to issue cross-domain requests.

Under the covers, that cross domain request is actually being issued by dynamically loading a new script tag in the DOM, even though everything's still handled through the $.ajax() function. It's workable, of course, but since there are no XMLHTTPRequests involved, the behaviour is apparently somewhat different. In my particular case, I was wiring up  'beforeSend and 'complete' event handlers to show and hide a spinner across the lifetime of the request; the 'complete' handler was firing fine but the 'beforeSend' handler wasn't firing at all.

A response by SLaks to a Stack Overflow post reminded me the request mechanism with JSONP is different and implied 'beforeSend' wouldn't work with this setup. His response instead suggested showing the spinner after the $.ajax call and this did the trick for me. 

Tuesday, 16 March 2010

Use EnableValidator to Manage an ASP.NET Validator in Client Script

I’m a big fan of the ASP.NET validator controls—not because they’re complete and wonderful but because they’re convenient when they work as expected and otherwise keep me on my toes when something out of the ordinary is required.

Today brought forth a requirement to hide the State field on a form when the country wasn’t set to Australia. Because the State field also has a RequiredFieldValidator attached, I would have to disable the validator server-side in some cases during the initial page load (depending on the data being pre-filled) and disable it client-side as the user interacts with the form. Failing to do so would prevent the form from posting back when the State field was hidden.

One of the main reasons I like these validation controls so much is because of the way they just work client-side and server side; in the client arena, everything occurs as Javascript and that was enough for me to assume, in this case, I could manage the validator through Javascript. I wasn’t certain this would be possible (or at least simple) but fortunately I won’t lose any sleep over this one tonight ;) As I found out, changes to a validation control in one context are even reflected in the other—nice!

The magic all happens with the mysterious ValidatorEnable(validator, bool) function. I have no idea where this function comes from and don’t really care but it allows you to enable or disable a validator in Javascript code by supplying the validator object and a boolean value indicating whether it should be enabled (true) or disabled (false).

It’s important to remember the first parameter is the validator object itself, not its ID. You’ll need to locate the validator object by ID (or using some other means) of course, but that’s where the ClientID property comes in handy:

var stateRequiredFieldValidatorId = "<%=rqdState.ClientID %>";

For additional information check out the ancient (circa 2002) “ASP.NET Validation in Depth” article on MSDN. Jonas Bush also has a concise example.

Tuesday, 10 February 2009

How to Clear Your Browser Cache 101

If you're a web developer you should really know how to do this by now; if not, I'll let you off the hook...

Quick and easy--always try this first:
CTRL-F5 (note, I didn't say SHIFT-F5 and I didn't say F5 all by all itself). If this doesn't work, proceed.

Internet Explorer 7 & 8
  1. Tools -> Internet Options
  2. General tab, Browsing history section, Delete... button
  3. Temporary Internet Files button or Delete all... button
While you're in here, you may also want to consider changing your browser's caching behaviour. To do so, progress to step 2, above, but instead of clicking the Delete... button, click the Settings button. Change the 'Check for newer versions of stored pages' to 'Every time I visit the web page'. Note this may affect the time it takes to load web pages initially.

Clearing your cache this way clears content for all sites you've visited, which can be a hassle if you're only having problems with a site in development or a single site and you use this browser for day-to-day browsing. If you want a more refined, more accessible mechanism for clearing your browser cache, install the free IE Developer Toolbar. Last I heard, the next version will be built in to IE8 but, meanwhile, it Firebug's poor, uneducated, inbred third cousin for IE6/7 and it generally does the trick. High, high hopes for the IE8 version...

To clear your cache using the IE Developer Toolbar, follow these steps:
  1. Install the toolbar and make it visible using the little arrow icon
  2. Browse to the site you're struggling with
  3. From the toolbar menu, select Cache -> Clear Browser Cache for This Domain... and say yes
You can also toggle the Always Refresh from Server option from this menu and clear session cookies for the current domain.

Firefox
  1. Tools -> Options
  2. Network tab, cache section, Clear Now button
Alternatively, install the free Firefox Web Developer Toolbar and follow these steps:
  1. Make sure the toolbar is displayed (View -> Toolbars -> Web Developer Toolbar)
  2. Click the Miscellaneous button
  3. Clear Private Data -> Cache
Or, after completing step 2, select Clear Private Data -> All Private Data and include cookies and authenticated sessions (session cookies).

[Update: don't forget to also clear your Flash local storage cache.]


Custom-Built Microsoft Office SharePoint Server 2007 Branded Sites and Webpart Development - info@mediawole.com
 

Tuesday, 11 November 2008

Stacking Windowed Elements and Flash Movies with z-index and wmode in IE

There exists a common misconception z-index will solve all of your DHTML stacking problems. While this is true for DHTML elements, the assumption falls down in the face of Internet Explorer and the way this browser treats "windowed" components like drop down lists based on the select tag. Flash objects aren't immune but for different reasons. Firefox will honour stacking orders for all elements but this is not the case when you're dealing with IE6/7. Windowed elements maintain a different stacking order than DHTML elements (they're added to a different plane than your windows elements) and therein lies the problem.

IE7 is better than IE6 when it comes to the select element but Flash requires some additional finessing, which I'll detail below. If you're still supporting IE6, consider using an IFrame (perhaps dynamically positioned) between your windowed component and your DHTML object to mask the underlying windowed component. The concept is generally referred to as an "IFrame shim". Don't forget, IFrames can be configured with a transparent background; either way, simply point the IFrame URL at nothing. The IFrame is no longer a windowed element as of IE5.5; although some people don't like IFrames for security and because they're frames, in this case the IFrame is your friend because it bridges the gap between windowed elements and windowless elements. The one main gotcha with this approach is how the user interacts with an IFrame: button clicks and mouse actions will be sent to the IFrame and the underlying content won't be interactive while the IFrame is visible.

Flash--being Flash and an ActiveX-wrapped plugin--is a different beast. IE places embedded content in a DHTML layer above all other layers by default and this is not always where you want your Flash content. On the westernaustralia.com home page, a fancy combination of DHTML and Flash elements known as the Tourism Australia overlay sits above the wa.com Flash banner (it's only visible to international site visitors); the DHTML-based navigation likewise has to sit above the central "experience panel" and the global site selector drop down in the right column.

To reign in your Flash objects and their stacking order you need to use an extra parameter named wmode:

&lt;param name="wmode" value="opaque" />

By doing so the Flash object and surrounding DHTML elements will team up to honour z-index styles as you intend. It's also a good idea to add a corresponding wmode="opaque" attribute to your element tag. Opaque and transparent modes may compromise your animations and video--we haven't had any problem on our sites, however, and use both animations (the wipe) and video. 

Here are a few additional tips for dealing with Flash:
  • Add Flash elements to a predefined placeholder element using JavaScript to ensure the object is activated automatically (otherwise users will have to click the Flash movie to run it and movies that should play automatically without user interaction may not start when the page loads). SWFObject will do this for you but it's really easy to write the JavaScript into your existing .js files, thereby avoiding the need to weigh down your page with yet another script library.
  • Don't rely solely on the embed tag. Use the object tag for IE and nest an embed tag within it for Mozilla and everyone else.
  • Always explicitly configure the wmode parameter as a child of the object tag and an attribute of the element tag.
  • Avoid the wmode "transparent" setting unless your Flash movie actually has transparent sections. Use "opaque" in most cases to increase rendering performance. The default is "window".
  • Consider a Flash alternative like Silverlight ;-)
Microsoft's windowless vs windowed summary:

Adobe's semi-useful KB article about wmode:

How to create an IFrame shim:


Custom-Built Microsoft Office SharePoint Server 2007 Branded Sites and Webpart Development - info@mediawole.com

Friday, 1 June 2007

Everything you always wanted to know about Javascript but were too afraid to ask

I’ve been hacking with Javascript since ’97 or so and it always seemed so accessible I never thought about sitting down to actually discover what it’s all about. Between AJAX and widgets and funky calendar controls I've recently came across constructs I'd never seen before—admittedly Javascript is, even after nearly x years, a mystery to me. Or rather it was, until I read this article:

http://odetocode.com/Articles/473.aspx

The article is reasonably short, very concise and explains a lot about the language itself. Better still, the article is targeted at .NET C# developers who know more about classes and method than prototypes. The examples are great as well.

Looks like Javascript isn’t going away any time soon so I’d suggest this article as a great intro to the rest of your life as a Javascript developer ;-)

Wednesday, 16 May 2007

A comprehensive review of how to make an anchor tag do nothing

What's the best way to fire a javascript event from an anchor tag without mucking up your navigation? For that matter, what's the best way to do anchor tags all together? Have a look at the tests below and review the source before making your own decision (you might want to copy the source out to your html file).

Here's a few things to consider:

  • We do not usually want a link to return the caret to the top of the page.
    Javascript may be disabled on the client side (but all solutions presented here are javascript-dependent)
  • Microsoft apparently recommends against using javascript: calls from within the HREF element of an anchor tag and its use may also impact accessibility.
  • If using a click event handler, place a hash in the href attribute and a return false; as the final code for the onClick attribute; ensure any preceding function calls can be interpreted and executed or the return false may never be reached.
  • Alternatively, use another tag (not an anchor tag) and its onClick attribute.

a (without href) (no underline)
a href="wwww.mysite.com" (normal link)
a href="" (opens containing folder in IE (when run without a web server); scrolls to top of page in FF)
a href="#" (scrolls to top of page)
a href="javascript:" (works in IE but pops up javascript console in FF)
a href="javascript: return false;" (javascript error)
*
a href="javascript: void(0);" (works but may cause problems)
*
a href="javascript: myFunc();" (works if myFunc () is defined - myFunc does not need to return false)
a href="javascript: myUndefinedFunc ();" (javascript error)
a href="top" (scrolls to top of page)
a href="null" (no underline)
* a href="#bookmark" (works if a name is defined immediately above)
a onclick="return false;" (without href) (no underline)
*
a href="" onclick="return false;" (works)
*
a href="#" onclick="return false;" (works)
a href="#" onclick="myBrokenFunc; return false;" (javascript error and scroll to top because return false never executes)
*
a href="#" onclick="myOnClickFunc (); return false;" (works)
* styled span (works but doesn't "select" on click or change color on visit)

* = A preferred way to do nothing links

Tuesday, 17 April 2007

Javascript Debugging in VS2005

See this link for a useful article on Javascript debugging with Visual Studio 2005. I managed to get it working using a basic page consisting of a single button with an OnClientClick attribute calling a simple inline function. I also played around with using the Javascript debugger; statement to break into the runtime document, which proved handy when attaching to the running server process instead of starting off via F5.

By the way, Firebug has a Javascript debugger and while it's pretty clunky, it might help get you out of a jam (Firebug is useful for so many other reasons as well).

http://blogs.msdn.com/webdevtools/archive/2007/03/08/jscript-debugging-in-visual-studio-2005.aspx