summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/oauth/oauth.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tcllib/modules/oauth/oauth.tcl')
-rw-r--r--tcllib/modules/oauth/oauth.tcl291
1 files changed, 291 insertions, 0 deletions
diff --git a/tcllib/modules/oauth/oauth.tcl b/tcllib/modules/oauth/oauth.tcl
new file mode 100644
index 0000000..b6ab07e
--- /dev/null
+++ b/tcllib/modules/oauth/oauth.tcl
@@ -0,0 +1,291 @@
+# !/bin/sh
+# the next line will restart with tclsh wherever it is \
+exec tclsh "$0" "$@"
+
+# oauth.tcl -*- tcl -*-
+# This module pretend give full support to API version 1.1 of Twitter
+# according to API v1.1’s Authentication Model
+#
+# Copyright (c) 2014 Javier Pérez - <hxm@eggdrop.es>
+# gave to tcllib
+#
+# About OAuthv1.0a
+# There are 3 steps we need complete to get authenticated with OAuth.
+# Steps:
+# 1. Authorizing a request: we need 7 parameters.
+# 1.1 Consumer key (oauth_consumer_key) from your app (dev.twitter.com/apps)
+# 1.2 Nonce (oauth_nonce) unique&random token autogenerated by base64 32bits
+# 1.3 Signature (oauth_signature) all the other requests and 2 secret values
+# trought a signing algorithm.
+# 1.4 Signature method (oauth_signature_method) which is HMAC-SHA1
+# 1.5 Timestamp (oauth_timestamp) time in unix format of the request
+# 1.6 Token (oauth_token) a parameter you can obtain in your account settings
+# 1.7 Version (oauth_version) the OAuth version, actually 1.0
+
+# TODO: create online documentation
+
+package require Tcl 8.5
+package provide oauth 1
+
+package require http
+package require tls
+package require base64
+package require sha1
+
+http::register https 443 ::tls::socket
+
+namespace eval ::oauth {
+ namespace export query
+
+ variable commands [namespace export]
+ variable project {OAuth1.0}
+ variable version [package present oauth]
+ variable description {OAuth authentication for Twitter support.}
+ variable author {Javier Pérez <hxm@eggdrop.es>}
+ # AK: changed to ISO date format.
+ variable created {2012-12-30, published 2014-02-10}
+ variable script [info script]
+ variable contact "$project $version ~ $description ($author)"
+
+ variable oauth
+ if {![info exists oauth]} {
+ array set oauth {
+ -accesstoken {}
+ -accesstokensecret {}
+ -consumerkey {}
+ -consumersecret {}
+ -debug 0
+ -oauthversion 1.0
+ -proxyhost {}
+ -proxyport {}
+ -ratelimit 1
+ -signmethod HMAC-SHA1
+ -timeout 6000
+ -urlencoding utf-8
+ }
+ set oauth(-useragent) "Mozilla/5.0\
+ ([string totitle $::tcl_platform(platform)]; U;\
+ $::tcl_platform(os) $::tcl_platform(osVersion))\
+ oauth/${version} Tcl/[package provide Tcl]"
+ }
+}
+
+# config --
+#
+# See documentation for details.
+#
+# Arguments:
+# args options parsed by the procedure.
+# Results:
+# This procedure returns the array with the current configuration
+# In order to create an array with the result of this procedure you can do
+# it in this way: array set settings [oauth::config ...]
+proc ::oauth::config {args} {
+ variable oauth
+ set options [array names oauth -*]
+ set usage [join $options {, }]
+ if {$args eq {}} {
+ return [array get oauth]
+ }
+ foreach {flag value} $args {
+ set optionflag [lsearch -inline -nocase $options $flag]
+ if {$optionflag eq ""} {
+ Error "Unknown option \"${flag}\", must be: $usage" BAD OPTION
+ }
+ set oauth($optionflag) $value
+ }
+ return [array get oauth]
+}
+
+# header --
+# Following OAuth1.0a rules, this procedure collects all
+# information required for get the authentication. All we need
+# is a header for our api queries with our user and app
+# information for the verification of who we are. Collect it,
+# encode it as the protocol says and add it to the geturl
+# command. If you want, you can use this procedure for your
+# own queries, just use it as header. Example:
+# http::geturl $twitter(url) -header [oauth::header <...>] <...>
+#
+# You can get more information about how twitter api works reading this:
+# https://dev.twitter.com/overview/documentation
+#
+# Arguments:
+# baseURL: full url path of twitter api. If it should be sent
+# as GET, add the query string.
+# postQuery: arguments passed at the request body as POST. This
+# should be in http query format.
+# Result:
+# This proc will return a list of values like this:
+# Authorization:
+# OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog",
+# oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
+# oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",
+# oauth_signature_method="HMAC-SHA1",
+# oauth_timestamp="1318622958",
+# oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
+# oauth_version="1.0"
+proc ::oauth::header {baseURL {postQuery ""}} {
+ variable oauth
+
+ if {$oauth(-signmethod) eq ""} {
+ Error "ERROR: invalid argument for -signmethod." BAD SIGN-METHOD
+ }
+ if {[package vcompare $oauth(-oauthversion) 1.0] != 0} {
+ Error "ERROR: this script only supports oauth_version 1.0" \
+ BAD OAUTH-VERSION
+ }
+ if {$oauth(-consumerkey) eq ""} {
+ Error "ERROR: please define your consumer key.\
+ [namespace current]::config -consumerkey <...>" \
+ BAD CONSUMER-KEY
+ }
+ if {$oauth(-accesstoken) eq ""} {
+ Error "ERROR: please define your app's access token.\
+ [namespace current]::config -accesstoken <...>" \
+ BAD ACCESS-TOKEN
+ }
+
+ set randomKey [sha1::sha1 [expr {[clock milliseconds] + round(rand()*50000)}]]
+ set timestamp [clock seconds]
+
+ lappend paramList "oauth_consumer_key=$oauth(-consumerkey)"
+ lappend paramList "oauth_nonce=$randomKey"
+ lappend paramList "oauth_signature_method=$oauth(-signmethod)"
+ lappend paramList "oauth_timestamp=$timestamp"
+ lappend paramList "oauth_token=$oauth(-accesstoken)"
+ lappend paramList "oauth_version=$oauth(-oauthversion)"
+
+ if {$postQuery eq {}} {
+ set url [lindex [split $baseURL {?}] 0]
+ set queryString [lindex [split $baseURL {?}] 1]
+ foreach argument [split $queryString {&}] {
+ lappend paramList $argument
+ }
+ set httpMethod {GET}
+ } else {
+ set url $baseURL
+ set httpMethod {POST}
+ }
+
+ foreach parameter $paramList {
+ set key [lindex [split $parameter {=}] 0]
+ set value [join [lrange [split $parameter {=}] 1 end] {=}]
+ lappend header "${key}=\"${value}\""
+ }
+ set paramString [join [lsort -dictionary $paramList] {&}]
+
+ lappend baseList $httpMethod
+ lappend baseList [PercentEncode $url]
+ lappend baseList [PercentEncode $paramString]
+ set signString [join $baseList {&}]
+
+ set signKey "[PercentEncode $oauth(-consumersecret)]&[PercentEncode $oauth(-accesstokensecret)]"
+ set signature [base64::encode [sha1::hmac -bin -key $signKey $signString]]
+
+ lappend header "oauth_signature=\"[PercentEncode $signature]\""
+ if {$oauth(-debug) == 1} {
+ puts {oauth::header: Authorization Oauth}
+ foreach line $header {
+ puts "\t$line"
+ }
+ puts "\nBaseString: $signString"
+ }
+ return "Authorization [list [concat OAuth [join [lsort -dictionary $header] {, }]]]"
+}
+
+# query --
+# Sends to oauth API url the proper oauth header and querybody
+# returning the raw data from Twitter for your parse.
+# Arguments:
+# baseURL api host URL with ?arguments if it's a GET request
+# postQuery POST query if it's a POST query
+# Result:
+# The result will be list with 2 arguments.
+# The first argument is an array with the http's header
+# and the second one is JSON data received from the server. The header is
+# very important because it reports your rest API limit and will
+# inform you if you can get your account suspended.
+proc ::oauth::query {baseURL {postQuery ""}} {
+ variable oauth
+ if {$oauth(-consumerkey) eq ""} {
+ Error "ERROR: please define your consumer key.\
+ [namespace current]::config -consumerkey <...>" \
+ BAD CONSUMER-KEY
+ }
+ if {$oauth(-consumersecret) eq ""} {
+ Error "ERROR: please define your app's consumer secret.\
+ [namespace current]::config -consumersecret <...>" \
+ BAD CONSUMER-SECRET
+ }
+ if {$oauth(-accesstoken) eq ""} {
+ Error "ERROR: please define your access token.\
+ [namespace current]::config -accesstoken <...>" \
+ BAD ACCESS-TOKEN
+ }
+ if {$oauth(-accesstokensecret) eq ""} {
+ Error "ERROR: please define your app's access token secret.\
+ [namespace current]::config -accesstokensecret <...>" \
+ BAD ACCESS-TOKEN-SECRET
+ }
+ if {$postQuery eq ""} {
+ set url [lindex [split $baseURL {?}] 0]
+ set queryString [join [lrange [split $baseURL {?}] 1 end] {?}]
+ set httpMethod {GET}
+ } else {
+ set url $baseURL
+ set httpMethod {POST}
+ }
+
+ if {$httpMethod eq {GET}} {
+ if {$queryString ne {}} {
+ append url ? $queryString
+ }
+ set requestBody {}
+ } else {
+ set requestBody $queryString
+ }
+ if {$queryString ne {}} {
+ set headerURL ${url}?${queryString}
+ } else {
+ set headerURL $url
+ }
+
+ set header [header $headerURL]
+
+ http::config \
+ -proxyhost $oauth(-proxyhost) \
+ -proxyport $oauth(-proxyport) \
+ -useragent $oauth(-useragent)
+
+ set token [http::geturl $baseURL \
+ -headers $header \
+ -query $requestBody \
+ -method $httpMethod \
+ -timeout $oauth(-timeout)]
+ set ncode [http::ncode $token]
+ set data [http::data $token]
+ upvar #0 $token state
+ lappend result [array names state]
+ lappend result $data
+ http::cleanup $token
+
+ return $result
+}
+
+
+# PercentEncode --
+# Encoding process in http://tools.ietf.org/html/rfc3986#section-2.1
+# for Twitter authentication. (http::formatQuery is lowcase)
+proc ::oauth::PercentEncode {string} {
+ set utf8String [encoding convertto utf-8 $string]
+ return [string map {"\n" "%0A"} \
+ [subst [regsub -all \
+ {[^-A-Za-z0-9._~\n]} $utf8String \
+ {%[format "%02X" [scan "\\\0" "%c"]]}]]]
+}
+
+proc ::oauth::Error {string args} {
+ return -code error -errorcode [linsert $args 0 OAUTH] $string
+}
+return