Dart implementation of https://oauth.net/core/1.0a
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:convert'; | |
import 'dart:math'; | |
import 'package:crypto/crypto.dart'; | |
import 'package:kaiteki/model/http_method.dart'; | |
final DateTime _epoch = DateTime(1970); | |
/// Returns the seconds since January 1st 1970, required for the timestamp | |
/// parameter. | |
int secondsSinceEpoch() { | |
return (DateTime.now().difference(_epoch)).inSeconds; | |
} | |
/// Generates a random string of characters, required for the nonce parameter. | |
String generateNonce([int length = 32]) { | |
final random = Random.secure(); | |
const chars = | |
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; | |
final buffer = StringBuffer(); | |
for (var i = 0; i < length; i++) { | |
final j = random.nextInt(chars.length - 1); | |
buffer.write(chars[j]); | |
} | |
return buffer.toString(); | |
} | |
/// Generates a signature with HMAC-SHA1. | |
String generateHmacSha256Signature( | |
HttpMethod method, | |
Uri url, | |
Map<String, String> parameters, | |
String secret, | |
) { | |
final encodedSecret = encode(secret); | |
final key = utf8.encode(encodedSecret + "&"); | |
final hmac = Hmac(sha1, key); | |
final data = getSignatureString(method, url, parameters); | |
final digest = hmac.convert(utf8.encode(data)); | |
return base64.encode(digest.bytes); | |
} | |
String getSignatureString( | |
HttpMethod method, | |
Uri url, | |
Map<String, String> parameters, | |
) { | |
final paramStr = parameters.entries // | |
.map((kv) => kv.key + "=" + encode(kv.value)) | |
.join("&"); | |
final normalizedUrl = normalizeUrl(url); | |
return [ | |
method.toMethodString(), | |
encode(normalizedUrl), | |
encode(paramStr), | |
].join("&"); | |
} | |
String encode(String value) { | |
final buffer = StringBuffer(); | |
final regex = RegExp("[0-9A-Za-z-._~]"); | |
for (var i = 0; i < value.length; i++) { | |
final char = value[i]; | |
if (regex.hasMatch(char)) { | |
buffer.write(char); | |
} else { | |
for (final byte in utf8.encode(char)) { | |
final hex = byte.toRadixString(16).padLeft(2, '0').toUpperCase(); | |
buffer | |
..write("%") | |
..write(hex); | |
} | |
} | |
} | |
return buffer.toString(); | |
} | |
String normalizeUrl(Uri url) { | |
final scheme = url.scheme.toLowerCase(); | |
final isRedundantPort = (scheme == "http" && url.port == 80) || | |
(scheme == "https" && url.port == 443); | |
return Uri( | |
scheme: url.scheme, | |
host: url.host, | |
port: isRedundantPort ? null : url.port, | |
path: url.path, | |
).toString().toLowerCase(); | |
} | |
/// Returns a complete authorization header for OAuth v1. | |
/// | |
/// Automatically adds missing parameters like nonce, signature and timestamp. | |
String generateAuthorizationHeader( | |
HttpMethod method, | |
Uri url, | |
Map<String, String> parameters, | |
String secret, | |
) { | |
parameters.addAll({ | |
"oauth_nonce": generateNonce(), | |
"oauth_version": "1.0", | |
"oauth_timestamp": secondsSinceEpoch().toString(), | |
"oauth_signature_method": "HMAC-SHA1", | |
}); | |
parameters = _sortMap(parameters); // Sort properly before generating sig | |
parameters["oauth_signature"] = generateHmacSha256Signature( | |
method, | |
url, | |
parameters, | |
secret, | |
); | |
parameters = _sortMap(parameters); // Sort properly before generating sig | |
return getAuthorizationHeader(parameters); | |
} | |
String getAuthorizationHeader(Map<String, String> parameters) { | |
final buffer = StringBuffer("OAuth ") | |
..writeAll( | |
parameters.entries.map( | |
(kv) => kv.key + '="' + encode(kv.value) + '"', | |
), | |
", ", | |
); | |
return buffer.toString(); | |
} | |
Map<T1, T2> _sortMap<T1 extends Comparable, T2>(Map<T1, T2> map) { | |
final sortedEntries = map.entries.toList() | |
..sort((a, b) => a.key.compareTo(b.key)); | |
return Map<T1, T2>.fromEntries(sortedEntries); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment