I have firebase function that processes webhook.
This function updates calendar event, updating of single event may be simultaneously.
I use Etag and If-Match header for prevent modification loss as described in doc.
But I still loss all changes except the last.
This code does multiple simultaneous updates with same event.
In result summary must be '10' but actually it is '2'.
What is wrong with it?
Code
const calendarId = 'calendarid';
const scopes = [
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/calendar.events",
];
const auth = new google.auth.GoogleAuth({ scopes });
const calendar = google.calendar({ version: "v3", auth });
...
const reproduce = async () => {
const {id: eventId} = await calendar.events.insert({
auth,
calendarId,
resource: {
summary: '1',
// some another resource data
},
});
const update = async () => {
const event = await calendar.events.get({
auth,
calendarId,
eventId,
});
console.log("***BEFORE***");
console.log("summary: " + event.summary);
console.log("etag: " + event.etag);
const previousSummary = event.summary;
const newSummary = parseInt(previousSummary) + 1;
const res = await calendar.events.patch({
auth,
calendarId,
eventId,
resource: {summary: newSummary.toString()},
}, {
headers: {
"If-Match": event.data.etag
}
});
console.log("***AFTER***");
console.log("summary: " + res.data.summary);
console.log("etag: " + res.data.etag);
}
let i = 0;
// simultaneously updates event
while (i++ < 10) {
update();
}
}
Output
> ***BEFORE***
> summary: 1
> etag: "3235006977030000"
> ***BEFORE***
> summary: 1
> etag: "3235006977030000"
> ***BEFORE***
> summary: 1
> etag: "3235006977030000"
> ***BEFORE***
> summary: 1
> etag: "3235006977030000"
> ***BEFORE***
> summary: 1
> etag: "3235006977030000"
> ***BEFORE***
> summary: 1
> etag: "3235006977030000"
> ***BEFORE***
> summary: 1
> etag: "3235006977030000"
> ***BEFORE***
> summary: 1
> etag: "3235006977030000"
> ***BEFORE***
> summary: 1
> etag: "3235006977030000"
> ***AFTER***
> summary: 2
> etag: "3235006998028000"
> Event updated successfully
> ***AFTER***
> summary: 2
> etag: "3235007000852000"
> Event updated successfully
> ***AFTER***
> summary: 2
> etag: "3235007002822000"
> Event updated successfully
> ***AFTER***
> summary: 2
> etag: "3235007003202000"
> Event updated successfully
> ***AFTER***
> summary: 2
> etag: "3235007004826000"
> Event updated successfully
> ***AFTER***
> summary: 2
> etag: "3235007009742000"
> Event updated successfully
> ***AFTER***
> summary: 2
> etag: "3235007011058000"
> Event updated successfully
> ***AFTER***
> summary: 2
> etag: "3235007014902000"
> Event updated successfully
> ***AFTER***
> summary: 2
> etag: "3235007018050000"
> Event updated successfully
I was able to reproduce your issue using Javascript in Apps Script.
The reason why you were able to update your event once (which makes your summary "2") is because of your async/await command in update().
Sample Code:
const update = async () => {
const event = await Calendar.Events.get(
calendarId,
eventId,
);
Logger.log("***BEFORE***");
Logger.log("summary: "+event.summary);
Logger.log("etag: "+event.etag);
const previousSummary = event.summary;
const newSummary = parseInt(previousSummary) + 1;
const res = await Calendar.Events.patch(
{summary: newSummary.toString()},
calendarId,
event.id,
{ "If-Match": event.etag }
);
Logger.log("***AFTER***");
Logger.log("summary: "+res.summary);
Logger.log("etag: "+res.etag);
//Logger.log(res);
}
let i = 0;
// simultaneously updates event
while (i++ < 10) {
update();
}
Output:
12:04:47 AM Notice Execution started
12:04:51 AM Info ***BEFORE***
12:04:51 AM Info summary: 1
12:04:51 AM Info etag: "3234758978180000"
12:04:51 AM Info ***BEFORE***
12:04:51 AM Info summary: 1
12:04:51 AM Info etag: "3234758978180000"
12:04:52 AM Info ***BEFORE***
12:04:52 AM Info summary: 1
12:04:52 AM Info etag: "3234758978180000"
12:04:52 AM Info ***BEFORE***
12:04:52 AM Info summary: 1
12:04:52 AM Info etag: "3234758978180000"
12:04:52 AM Info ***BEFORE***
12:04:52 AM Info summary: 1
12:04:52 AM Info etag: "3234758978180000"
12:04:53 AM Info ***BEFORE***
12:04:53 AM Info summary: 1
12:04:53 AM Info etag: "3234758978180000"
12:04:53 AM Info ***BEFORE***
12:04:53 AM Info summary: 1
12:04:53 AM Info etag: "3234758978180000"
12:04:53 AM Info ***BEFORE***
12:04:53 AM Info summary: 1
12:04:53 AM Info etag: "3234758978180000"
12:04:54 AM Info ***BEFORE***
12:04:54 AM Info summary: 1
12:04:54 AM Info etag: "3234758978180000"
12:04:54 AM Info ***BEFORE***
12:04:54 AM Info summary: 1
12:04:54 AM Info etag: "3234758978180000"
12:04:54 AM Info ***AFTER***
12:04:54 AM Info summary: 2
12:04:54 AM Info etag: "3234758983260000"
12:04:54 AM Info ***AFTER***
12:04:54 AM Info summary: 2
12:04:54 AM Info etag: "3234758983260000"
12:04:54 AM Info ***AFTER***
12:04:54 AM Info summary: 2
12:04:54 AM Info etag: "3234758983260000"
12:04:54 AM Info ***AFTER***
12:04:54 AM Info summary: 2
12:04:54 AM Info etag: "3234758983260000"
12:04:54 AM Info ***AFTER***
12:04:54 AM Info summary: 2
12:04:54 AM Info etag: "3234758983260000"
12:04:54 AM Info ***AFTER***
12:04:54 AM Info summary: 2
12:04:54 AM Info etag: "3234758983260000"
12:04:54 AM Info ***AFTER***
12:04:54 AM Info summary: 2
12:04:54 AM Info etag: "3234758983260000"
12:04:54 AM Info ***AFTER***
12:04:54 AM Info summary: 2
12:04:54 AM Info etag: "3234758983260000"
12:04:54 AM Info ***AFTER***
12:04:54 AM Info summary: 2
12:04:54 AM Info etag: "3234758983260000"
12:04:54 AM Info ***AFTER***
12:04:54 AM Info summary: 2
12:04:54 AM Info etag: "3234758983260000"
12:04:54 AM Notice Execution completed
Notice that Calendar.Events.get() method was executed 10 times before Calendar.Events.patch() was executed. Hence you were able to patch your event once.
I'm not that expert when it comes to Javascript Promises but you might want to remove your async/await in your update(), get() and patch().
Related
When trying to put to WebHDFS in order to create a file and write to it (using the following link: https://hadoop.apache.org/docs/r1.0.4/webhdfs.html#CREATE) I run into issues using httr.
Using RCurl or RWebHDFS is not possible because the target Hadoop cluster is secure.
Here is the code I have attempted to use:
library(httr)
r <- PUT("https://hadoopmgr1p.global.ad:14000/webhdfs/v1/user/testuser/temp/loadfile_testuser_2019-11-28_15_28_41411?op=CREATE&permission=755&user.name=testuser",
authenticate(":", "", type = "gssnegotiate"),
verbose())
testuser is a super user with permissions to R/W. I get the following error:
<- HTTP/1.1 400 Data upload requests must have content-type set to 'application/octet-stream'
<- Date: Fri, 29 Nov 2019 15:42:30 GMT
<- Date: Fri, 29 Nov 2019 15:42:30 GMT
<- Pragma: no-cache
<- X-Content-Type-Options: nosniff
<- X-XSS-Protection: 1; mode=block
<- Content-Length: 0
The error is pretty explanatory, so I then attempt to PUT with a content-type:
r <- PUT("https://hadoopmgr1p.global.ad:14000/webhdfs/v1/user/testuser/temp/loadfile_testuser_2019-11-28_15_28_41411?op=CREATE&permission=755&user.name=testuser",
authenticate(":", "", type = "gssnegotiate"),
content_type("application/octet-stream"),
verbose())
I get a success - however it is not truly successful:
<- Date: Fri, 29 Nov 2019 16:04:52 GMT
<- Cache-Control: no-cache
<- Expires: Fri, 29 Nov 2019 16:04:52 GMT
<- Date: Fri, 29 Nov 2019 16:04:52 GMT
<- Pragma: no-cache
<- Content-Type: application/json;charset=utf-8
<- X-Content-Type-Options: nosniff
<- X-XSS-Protection: 1; mode=block
<- Content-Length: 0
There is no file that was uploaded. Uploading a file with that first request, gives me another error:
<- HTTP/1.1 307 Temporary Redirect
<- Date: Fri, 29 Nov 2019 16:07:24 GMT
<- Cache-Control: no-cache
<- Expires: Fri, 29 Nov 2019 16:07:24 GMT
<- Date: Fri, 29 Nov 2019 16:07:24 GMT
<- Pragma: no-cache
<- Content-Type: application/json;charset=utf-8
<- X-Content-Type-Options: nosniff
<- X-XSS-Protection: 1; mode=block
Error in curl::curl_fetch_memory(url, handle = handle) :
necessary data rewind wasn't possible
The code in question:
library(httr)
temp_file <- httr::upload_file(lfs_temp_file, type = "text/plain")
r <- PUT("https://hadoopmgr1p.global.ad:14000/webhdfs/v1/user/testuser/temp/loadfile_testuser_2019-11-28_15_28_41411?op=CREATE&permission=755&user.name=testuser",
authenticate(":", "", type = "gssnegotiate"),
body=temp_file,
content_type("application/octet-stream"),
verbose())
Attempting the same command using curl works without issue:
curl -i -k -X PUT --negotiate -u : "https://hadoopmgr1p.global.ad:14000/webhdfs/v1/user/testuser/temp/loadfile_testuser_2019-11-28_15_28_4141?op=CREATE&permission=755&user.name=testuser"
This results in the following:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0HTTP/1.1 307 Temporary Redirect
Date: Thu, 28 Nov 2019 23:27:16 GMT
Cache-Control: no-cache
Expires: Thu, 28 Nov 2019 23:27:16 GMT
Date: Thu, 28 Nov 2019 23:27:16 GMT
Pragma: no-cache
Content-Type: application/json;charset=utf-8
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
WWW-Authenticate: Negotiate <stuff>/
Set-Cookie: hadoop.auth="<stuff>"; Path=/; Secure; HttpOnly
Location: https://hadoopmgr1p.global.ad:14000/webhdfs/v1/user/testuser/temp/loadfile_testuser_2019-11-28_15_28_4141?op=CREATE&data=true&user.name=testuser&permission=755
Content-Length: 0
Following the Location header lets us create the file successfully.
What am I doing wrong?
Thanks
Good work, including the curl output. I believe that answers it.
Your curl command uses PUT, and your httr command uses POST. Try https://www.rdocumentation.org/packages/httr/versions/1.4.1/topics/PUT .
Hint for future reference: POST commands are not typically used if you're specifying an exact location. That's what PUT is for.
httr is attempting to follow the redirect, and failing. To fix the issue, tell httr to stop following the location config(followlocation = 0L).
The PUT command will be as follows:
r <- PUT("https://hadoopmgr1p.global.ad:14000/webhdfs/v1/user/testuser/temp/
loadfile_testuser_2019-11-28_15_28_41411?op=CREATE&permission=755&user.name=testuser",
authenticate(":", "", type = "gssnegotiate"),
body=NULL,
config(followlocation = 0L),
verbose())
This should return a valid reponse with a Location header.
httr 1.4.1
R version 3.6.1 (also tried with 3.5.3)
Edit (adding verbose()) output.
I've got a request as follows:
r <- GET("https://my.cool.domain",add_headers(.headers = c('x-api-key' = 'abcdefg', 'Accept' = "text/csv")), verbose())
On my machine it responds with:
-> GET / HTTP/1.1
-> Host: https://my.cool.domain
-> User-Agent: libcurl/7.54.0 r-curl/4.2 httr/1.4.1
-> Accept-Encoding: deflate, gzip
-> x-api-key: abcdefg
-> Accept: text/csv
->
<- HTTP/1.1 200 OK
<- Date: Tue, 26 Nov 2019 17:50:15 GMT
<- Content-Type: text/csv
<- Content-Length: 24902
<- Connection: keep-alive
<- x-amzn-RequestId: ...
<- Content-Encoding: deflate
<- x-amz-apigw-id: ...
<- X-Amzn-Trace-Id: ...
Response [https://my.cool.domain]
Date: 2019-11-26 17:20
Status: 200
Content-Type: text/csv
Size: 209 kB
cats,dogs...
yes,no...
yes,yes...
no,no...
However on my colleague's machine (same version of httr and R, and also with an updated version of R) I get the following:
-> GET / HTTP/2
-> Host: https://my.cool.domain
-> User-Agent: libcurl/7.64.1 r-curl/4.2 httr/1.4.1
-> Accept-Encoding: deflate, gzip
-> x-api-key: abcdefg
-> Accept: text/csv
->
<- HTTP/2 200
<- date: Tue, 26 Nov 2019 17:46:17 GMT
<- content-type: application/json
<- content-length: 21501
<- x-amzn-requestid: ...
<- content-encoding: deflate
<- x-amz-apigw-id: ...
<- x-amzn-trace-id: ...
Response [https://my.cool.domain]
Date: 2019-11-26 17:30
Status: 200
Content-Type: application/json
Size: 377 kB
I'm working with the developer of the https://my.cool.domain domain and I can confirm that the request header params (x-api-key and 'Accept' = "text/csv") are perfect. And the request works on my machine, and several others, but not this one colleague's.
What's going wrong here and how can I debug this?
Thanks
This was fixed by doing httr::set_config(httr::config(http_version = 1.1)) to force 1.1.
I have this http request:
> Header contents:
> HTTP/1.1 200 OK
> Cache-Control: max-age=0, no-store
> Date: Wed, 17 Jul 2019 08:55:45 GMT
> Content-Length: 22049
> Content-Type: text/html; charset=utf-8
> Expires: Wed, 17 Jul 2019 08:54:46 GMT
> P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"
> Server: Microsoft-IIS/10.0
> Set-Cookie: uaid=d796f99cd2d0412896d89ff49e17f558; domain=login.live.com;secure= ;path=/;HTTPOnly= ;version=1
> Set-Cookie: MSPRequ=lt=1563353746&co=1&id=N; secure= ;path=/;HTTPOnly=;version=1
> Set-Cookie: MSCC=185.114.139.243-UA; expires=Mon, 10-Aug-2020 08:55:46 GMT;domain=login.live.com;secure= ;path=/;HTTPOnly=
> ;version=1
> Set-Cookie: OParams=11DYc12zw!m0m7KBb!DgFPYsJXqpdnsnm6nN3KgjlWTBX2lFFtVrtpWXroTonZhmkMvYlU!f3nP2nNmxl1e06ODEOqVKryMr2c8oUmEwI6X!pkrwGqp*d3yzT3VgKmGYMM8kan2MCPifbPjx!8Ww3OhjXTuJz6OCXClnzjw6BP2S8xT8jhvTPxOwiLO1aKqS65f2TXcEHLKmR7yHPQp773y8M$;
> domain=login.live.com;secure= ;path=/;HTTPOnly= ;version=1
> Set-Cookie: MSPOK=$uuid-3a02e0a2-6a86-42f0-ab2a-d8252a1a9567; domain=login.live.com;secure= ;path=/;HTTPOnly= ;version=1
> X-Frame-Options: deny
> PPServer: PPV: 30 H: BL02PF05B1B33B1 V: 0
> X-Content-Type-Options: nosniff
> Strict-Transport-Security: max-age=31536000
> X-XSS-Protection: 1; mode=block
What part of the Set-Cookie should be sent to the server?
I didn't find something about MSPRequ, MSCC, MSPOK. Thank for your answer.
Please, can you help me to figure out what is wrong with the signature? I really, can't understand what is the problem. I have been searching for the answer for over a month. If you give me an example, of the similar working
request or help me to understand the problem I would appreciate it so much!
library(RCurl)
library(httr)
library(rvest)
cons_key<-"ae1dd19212a84c4299f4b157462d32d7"
shared_secret<-"c0bc9938ea754f4ba6e926865b347fae"
encoded_url<-URLencode("http://platform.fatsecret.com/rest/server.api",reserved = T,repeated = T)
encoded_string<-URLencode("method=GET&oauth_consumer_key=cons_key&oauth_nonce=asdas4&oauth_signature_method=HMAC-SHA1&oauth_timestamp=15138110238&oauth_version=1.0",reserved = T,repeated = T)
text_string<-paste0("GET&",encoded_url,"&",encoded_string)
signature_oauth<-sha1_hash(key = "ae1dd19212a84c4299f4b157462d32d7&",string = text_string)
getForm("http://platform.fatsecret.com/rest/server.api",
method="GET",
oauth_consumer_key=cons_key,
oauth_signature=signature_oauth,
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1513811023",
oauth_nonce="asdas4",
oauth_version="1.0",.opts = list(verbose = TRUE))
The answer is:
Trying 34.225.169.9...
* Connected to platform.fatsecret.com (34.225.169.9) port 80 (#0)
> GET /rest/server.api method=GET&oauth_consumer_key=ae1dd19212a84c4299f4b157462d32d7&oauth_signature=rB2IYo5tZ%2BAUIsk%2BjlP0ahtn%2Bhk%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1513811023&oauth_nonce=asdas4&oauth_version=1.0 HTTP/1.1
Host: platform.fatsecret.com
Accept: */*
< HTTP/1.1 200 OK
< Date: Wed, 20 Dec 2017 23:05:44 GMT
< Content-Type: text/xml; charset=utf-8
< Content-Length: 377
< Connection: keep-alive
< Cache-Control: private
< Server: Microsoft-IIS/8.5
< X-AspNet-Version: 4.0.30319
< X-Powered-By: ASP.NET
<
* Connection #0 to host platform.fatsecret.com left intact
[1] "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<error
xmlns=\"http://platform.fatsecret.com/api/1.0/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://platform.fatsecret.com/api/1.0/ http://platform.fatsecret.com/api/1.0/fatsecret.xsd\">\r\n\t<code>8</code>\r\n\t<message>Invalid signature: oauth_signature 'rB2IYo5tZ+AUIsk+jlP0ahtn+hk='</message>\r\n</error>\r\n"
attr(,"Content-Type")
charset
"text/xml" "utf-8"
Hi I've been following this article to setup FreeRADIUS Google Dual Factor Authenticator
http://www.supertechguy.com/help/security/freeradius-google-auth
Hours of testing I still can't get it to work. If my /etc/pam.d/radiusd looks like the following it works well with
the following command
radtest test test localhost 18120 testing123
#
# /etc/pam.d/radiusd - PAM configuration for FreeRADIUS
#
# We fall back to the system default in /etc/pam.d/common-*
#
#include common-auth
#include common-account
#include common-password
#include common-session
However if it looks like the following
#
# /etc/pam.d/radiusd - PAM configuration for FreeRADIUS
#
# We fall back to the system default in /etc/pam.d/common-*
#
##include common-auth
##include common-account
##include common-password
##include common-session
auth requisite pam_google_authenticator.so forward_pass
auth required pam_unix.so use_first_pass
my log file says the following and auth fails.
rad_recv: Access-Request packet from host 127.0.0.1 port 43185, id=111, length=56
User-Name = "test"
User-Password = "test"
NAS-IP-Address = 127.0.1.1
NAS-Port = 18120
Thu Sep 26 16:38:19 2013 : Info: # Executing section authorize from file /etc/freeradius/sites-enabled/default
Thu Sep 26 16:38:19 2013 : Info: +- entering group authorize {...}
Thu Sep 26 16:38:19 2013 : Info: ++[preprocess] returns ok
Thu Sep 26 16:38:19 2013 : Info: ++[chap] returns noop
Thu Sep 26 16:38:19 2013 : Info: ++[mschap] returns noop
Thu Sep 26 16:38:19 2013 : Info: ++[digest] returns noop
Thu Sep 26 16:38:19 2013 : Info: [suffix] No '#' in User-Name = "test", looking up realm NULL
Thu Sep 26 16:38:19 2013 : Info: [suffix] No such realm "NULL"
Thu Sep 26 16:38:19 2013 : Info: ++[suffix] returns noop
Thu Sep 26 16:38:19 2013 : Info: [eap] No EAP-Message, not doing EAP
Thu Sep 26 16:38:19 2013 : Info: ++[eap] returns noop
Thu Sep 26 16:38:19 2013 : Info: [files] users: Matched entry DEFAULT at line 74
Thu Sep 26 16:38:19 2013 : Info: ++[files] returns ok
Thu Sep 26 16:38:19 2013 : Info: ++[expiration] returns noop
Thu Sep 26 16:38:19 2013 : Info: ++[logintime] returns noop
Thu Sep 26 16:38:19 2013 : Info: [pap] WARNING! No "known good" password found for the user. Authentication may fail because of this.
Thu Sep 26 16:38:19 2013 : Info: ++[pap] returns noop
Thu Sep 26 16:38:19 2013 : Info: Found Auth-Type = PAM
Thu Sep 26 16:38:19 2013 : Info: # Executing group from file /etc/freeradius/sites-enabled/default
Thu Sep 26 16:38:19 2013 : Info: +- entering group authenticate {...}
Thu Sep 26 16:38:19 2013 : Debug: pam_pass: using pamauth string <radiusd> for pam.conf lookup
Thu Sep 26 16:38:19 2013 : Debug: pam_pass: function pam_authenticate FAILED for <test>. Reason: Cannot make/remove an entry for the specified session
Thu Sep 26 16:38:19 2013 : Info: ++[pam] returns reject
Thu Sep 26 16:38:19 2013 : Info: Failed to authenticate the user.
Thu Sep 26 16:38:19 2013 : Info: Using Post-Auth-Type Reject
Thu Sep 26 16:38:19 2013 : Info: # Executing group from file /etc/freeradius/sites-enabled/default
Thu Sep 26 16:38:19 2013 : Info: +- entering group REJECT {...}
Thu Sep 26 16:38:19 2013 : Info: [attr_filter.access_reject] expand: %{User-Name} -> test
Thu Sep 26 16:38:19 2013 : Debug: attr_filter: Matched entry DEFAULT at line 11
Thu Sep 26 16:38:19 2013 : Info: ++[attr_filter.access_reject] returns updated
Thu Sep 26 16:38:19 2013 : Info: Delaying reject of request 0 for 1 seconds
Thu Sep 26 16:38:19 2013 : Debug: Going to the next request
Thu Sep 26 16:38:19 2013 : Debug: Waking up in 0.9 seconds.
Thu Sep 26 16:38:20 2013 : Info: Sending delayed reject for request 0
Sending Access-Reject of id 111 to 127.0.0.1 port 43185
Thu Sep 26 16:38:20 2013 : Debug: Waking up in 4.9 seconds.
Thu Sep 26 16:38:25 2013 : Info: Cleaning up request 0 ID 111 with timestamp +3
Thu Sep 26 16:38:25 2013 : Info: Ready to process requests.
I'm using Ubuntu latest
Does anyone know what the issue here?
Many Thanks
After so much internet surfing and forum hunting I manage to fix this problem. If anyone else having this issue this might help them :)
Thu Sep 26 16:38:19 2013 : Debug: pam_pass: using pamauth string <radiusd> for pam.conf lookup
Thu Sep 26 16:38:19 2013 : Debug: pam_pass: function pam_authenticate FAILED for <test>. Reason: Cannot make/remove an entry for the specified session
The above line actually means a auth fail, even though it doesn't sound like it, also it could mean that .google_authenticator file in the user's home directory isn't accessible.
FreeRadius log file not help you much with this issue, but have a look through /var/log/secure on CentOS and /var/log/auth.log in Ubuntu. This will explain which is the issue.
Issue with my system was my time was out and my random generated number by Google Dual Factor Authenticator application on my iPhone wasn't valid. I had to install NTP and change my servers time to the correct time which fixed the issue!!!!
hope this help someone else :)
The how-to on Super Tech Guy's page (http://www.supertechguy.com/help/security/freeradius-google-auth) has a typo.
DEFAULT Auth-Type := PAM
should be
DEFAULT Auth-Type = PAM
I don't know why he put a colon in there, but removing it fixed my issue.
This was after I made sure the server had the correct time (and timezone), which it didn't. So thanks for that suggestion too!