Skip to content

Instantly share code, notes, and snippets.

@parasyte parasyte/README.md
Last active Dec 17, 2015

Embed
What would you like to do?
PubNub pam.py

REST API

The REST API provides secure administrative tasks for managing and auditing permissions (aka Access Control List, or ACL). All of the REST APIs require a cryptographic signature and a timestamp that is reasonably close to NTP time.

Timestamp

The timestamp is an HTTP query parameter following the Unix Time convention (number of seconds elapsed since 00:00:00 January 1, 1970 UTC). The service will reject any request containing a timestamp that is +/- 60 seconds from the current time according to NTP. This strict requirement prevents certain kinds of replay attacks.

Cryptographic Signature

The signature is an HTTP query parameter computed with the HMAC+SHA256 algorithm and encoded with URL-safe Base64. The message to be signed is composed of all query parameters (with obvious exception of the signature itself) plus the PubNub account subscribe and publish keys, and the REST resource. The signing key is the PubNub account secret key. Full details for signature computation are provided later.

Computing the Signature

<sign> is computed using HMAC+SHA256 with the user's secret key as the signing key, and the request string as the message. The request string is composed of the request query parameters concatenated to the subscribe key, publish key, and action (grant or audit) in the following format string:

"{sub_key}\n{pub_key}\n{action}\n{query_string}"

Query string parameters must be sorted lexicographically (case-sensitive) by key. Secondly, all characters in the query string parameters must be percent-encoded except alphanumeric, hyphen, underscore, and period; E.g. all characters matching the RegExp /[^0-9a-zA-Z\-_\.]/. Space characters must be replaced by %20 (NOT + character). Each key-value pair must be separated by ampersand characters. Unicode characters must be broken up into UTF-8 encoded bytes before percent-encoding.

Here is an example of a query string containing unicode characters:

auth=joker&r=1&w=1&ttl=60&timestamp=123456789&PoundsSterling=£13.37

And here is the same query string after sorting and percent-encoding:

PoundsSterling=%C2%A313.37&auth=joker&r=1&timestamp=123456789&ttl=60&w=1

Here is a full example message:

demo
demo
grant
auth=jay&channel=jays_channel&r=1&timestamp=123456789&ttl=1440&w=1

Let's imagine the demo account's secret key is:

wMfbo9G0xVUG8yfTfYw5qIdfJkTd7A

The signature generated for this request is Base64 encoded using the "URL safe" characters - and _ replacing + and / respectively:

v2rgQQ1eFzk8omugFV9V1_eKRUvvMv9jyC9Z-L1ogdw=

This signature is then percent-encoded according to standard query parameter percent-encoding practices. E.g. the = character is transformed into %3D.

#!/usr/bin/env python
from base64 import urlsafe_b64encode
from hashlib import sha256
from urllib import quote
from urllib import urlopen
import hmac
import time
def sign(key, msg):
"""Calculate a signature by secret key and message."""
return hmac.new(
key.encode("utf-8"), msg.encode("utf-8"), sha256
).digest()
def go(method, query, secretkey, subkey, pubkey, origin="pubsub.pubnub.com"):
"""Make an authenticated request."""
timestamp = int(time.time())
query['timestamp'] = timestamp
params = "&".join([
x + "=" + quote(str(query[x]), safe="") for x in sorted(query)
])
signature = urlsafe_b64encode(sign(
secretkey,
"{subkey}\n{pubkey}\n{method}\n{params}".format(
subkey=subkey,
pubkey=pubkey,
method=method,
params=params
)
))
url = ("http://" + origin + "/v1/auth/" + method + "/sub-key/" +
subkey + "?" + params + "&signature=" + quote(signature, safe=""))
print "#", url
opened = urlopen(url)
return opened.read() or opened.info().getheader('X-Response-Body')
if __name__ == "__main__":
import argparse
import sys
import json
import textwrap
parser = argparse.ArgumentParser(
description="PAM REST Client",
formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument(
'-o', '--origin',
default="pubsub.pubnub.com",
help=textwrap.dedent("""\
PubSub origin domain.
Examples:
pubsub-napac.pubnub.com
""")
)
parser.add_argument(
'-k', '--secretkey',
default="pam",
help=textwrap.dedent("""\
Account secret key.
""")
)
parser.add_argument(
'-s', '--subkey',
default="pam",
help=textwrap.dedent("""\
Account subscribe key.
""")
)
parser.add_argument(
'-p', '--pubkey',
default="pam",
help=textwrap.dedent("""\
Account publish key.
""")
)
parser.add_argument(
'action',
choices=[ "grant", "audit" ],
help=textwrap.dedent("""\
PAM REST action
""")
)
parser.add_argument(
'json',
nargs="*",
help=textwrap.dedent("""\
PAM query parameter options
Examples:
'{ "channel" : "foo,bar" }'
'{ "channel" : "foo", "auth" : "bar", "r" : 1, "ttl" : 10 }'
""")
)
args = parser.parse_args()
print json.dumps(json.loads(go(
args.action,
json.loads(" ".join(args.json) or "{}"),
args.secretkey,
args.subkey,
args.pubkey,
origin=args.origin
)), indent=4)
from base64 import urlsafe_b64encode
from hashlib import sha256
from urllib import quote
import hmac
import time
def sign(key, msg):
"""Calculate a signature by secret key and message."""
return hmac.new(
key.encode("utf-8"), msg.encode("utf-8"), sha256
).digest()
def go(method, query, secretkey, subkey, pubkey):
"""Make an authenticated request."""
if 'timestamp' not in query:
query['timestamp'] = int(time.time())
params = "&".join([
x + "=" + quote(str(query[x]), safe="") for x in sorted(query)
])
msg = "{subkey}\n{pubkey}\n{method}\n{params}".format(
subkey=subkey,
pubkey=pubkey,
method=method,
params=params
)
signature = urlsafe_b64encode(sign(
secretkey,
msg
))
print "Message:\n\t", msg.replace("\n", "\n\t")
print
print "signature:\n\t", signature
if __name__ == "__main__":
go(
"grant",
{
"channel" : "hello_my_channel,hello_my_channel1,hello_my_channel2",
"r" : 1,
"w" : 1,
"ttl" : 5,
"timestamp": 1376504023
},
"sec-c-YjFmNzYzMGMtYmI3NC00NzJkLTlkYzYtY2MwMzI4YTJhNDVh",
"sub-c-a478dd2a-c33d-11e2-883f-02ee2ddab7fe",
"pub-c-a2650a22-deb1-44f5-aa87-1517049411d5"
)
@stephenlb

This comment has been minimized.

Copy link

stephenlb commented May 24, 2013

PubNub Access Manager

Use the Python module like this:

pam.go(
    "grant", 
    { 'channel':'jay', 'auth':'jay,stephen,geremy,rongchao', 'r':1, 'w':1 }, 
    secret_key, 
    sub_key, 
    pub_key
)

Setup Your Keys:

import pubnub_am as am
sub_key = "sub-c-ce685120-be8c-11e2-b58e-02ee2ddab7fe"
pub_key = "pub-c-7fc18a20-e244-45f3-b1b1-79ff6fdbd9ab"
secret_key = "sec-c-NDg1ZjRmNzMtMGYwYS00ZTJhLTgyNzAtYzE1YmRlNmNjNjFk"

Grant read & write:

pam.go("grant", { 'channel':'jay', 'auth':'jay,stephen,geremy,rongchao', 'r':1, 'w':1 }, secret_key, sub_key, pub_key)

Revoke write (Grant read):

pam.go("grant", { 'channel':'jay', 'auth':'jay,stephen,geremy,rongchao', 'r':1, 'w':0 }, secret_key, sub_key, pub_key)

Revoke all:

pam.go("grant", { 'channel':'jay', 'auth':'jay,stephen,geremy,rongchao', 'r':0, 'w':0 }, secret_key, sub_key, pub_key)

Lookup all grants on subkey:

pam.go("audit", {}, secret_key, sub_key, pub_key)

Lookup grants on channels:

pam.go("audit", { 'channel':'jay,jay-pn-pres' }, secret_key, sub_key, pub_key)

Lookup grants on auth:

pam.go("audit", { 'channel':'jay', 'auth':'jay,rongchao' }, secret_key, sub_key, pub_key)

TTL

Permissions can expire automatically, like sessions! Use the ttl parameter to change the expiration time. Its value is represented in minutes:

Default: 1440    (24 hours)
Minimum: 1       (1 minute)
Maximum: 525600  (365 days)
Special: 0       (infinity)

Expire in one hour

pam.go("grant", { 'channel':'jay', 'auth':'jay', 'r':1, 'w':1, 'ttl':60 }, secret_key, sub_key, pub_key)

Expire in 30 days

pam.go("grant", { 'channel':'jay', 'auth':'jay', 'r':1, 'w':1, 'ttl':60*24*30 }, secret_key, sub_key, pub_key)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.