Add support for client credentials grant (#45)

* Add client credentials grant

* Add test

* Export client credentials grant

* Add client credentials grant to readme
diff --git a/README.md b/README.md
index 053f88b..567d73f 100644
--- a/README.md
+++ b/README.md
@@ -12,12 +12,13 @@
 
 OAuth2 provides several different methods for the client to obtain
 authorization. At the time of writing, this library only supports the
-[AuthorizationCodeGrant][] and [resourceOwnerPasswordGrant][] methods, but
+[AuthorizationCodeGrant][], [clientCredentialsGrant][] and [resourceOwnerPasswordGrant][] methods, but
 further methods may be added in the future. The following example uses this
 method to authenticate, and assumes that the library is being used by a
 server-side application.
 
 [AuthorizationCodeGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.AuthorizationCodeGrant
+[clientCredentialsGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.clientCredentialsGrant
 [resourceOwnerPasswordGrant]: http://www.dartdocs.org/documentation/oauth2/latest/index.html#oauth2/oauth2.resourceOwnerPasswordGrant
 
 ## Authorization Code Grant
@@ -112,6 +113,41 @@
 }
 ```
 
+## Client Credentials Grant
+```dart
+// This URL is an endpoint that's provided by the authorization server. It's
+// usually included in the server's documentation of its OAuth2 API.
+final authorizationEndpoint =
+    Uri.parse("http://example.com/oauth2/authorization");
+
+// The OAuth2 specification expects a client's identifier and secret
+// to be sent when using the client credentials grant.
+//
+// Because the client credentials grant is not inherently associated with a user,
+// it is up to the server in question whether the returned token allows limited
+// API access.
+//
+// Either way, you must provide both a client identifier and a client secret:
+final identifier = "my client identifier";
+final secret = "my client secret";
+
+// Calling the top-level `clientCredentialsGrant` function will return a
+// [Client] instead.
+var client = await oauth2.clientCredentialsGrant(
+    authorizationEndpoint, identifier, secret);
+
+// With an authenticated client, you can make requests, and the `Bearer` token
+// returned by the server during the client credentials grant will be attached
+// to any request you make.
+var response = await client.read("https://example.com/api/some_resource.json");
+
+// You can save the client's credentials, which consists of an access token, and
+// potentially a refresh token and expiry date, to a file. This way, subsequent runs
+// do not need to reauthenticate, and you can avoid saving the client identifier and
+// secret.
+await credentialsFile.writeAsString(client.credentials.toJson());
+```
+
 ## Resource Owner Password Grant
 
 ```dart
diff --git a/lib/oauth2.dart b/lib/oauth2.dart
index 3b81cf1..dfd2d57 100644
--- a/lib/oauth2.dart
+++ b/lib/oauth2.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 export 'src/authorization_code_grant.dart';
+export 'src/client_credentials_grant.dart';
 export 'src/resource_owner_password_grant.dart';
 export 'src/client.dart';
 export 'src/credentials.dart';
diff --git a/lib/src/client_credentials_grant.dart b/lib/src/client_credentials_grant.dart
new file mode 100644
index 0000000..3c46935
--- /dev/null
+++ b/lib/src/client_credentials_grant.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:http/http.dart' as http;
+import 'package:http_parser/http_parser.dart';
+
+import 'client.dart';
+import 'handle_access_token_response.dart';
+import 'utils.dart';
+
+/// Obtains credentials using a [client credentials grant](https://tools.ietf.org/html/rfc6749#section-1.3.4).
+///
+/// This mode of authorization uses the client's [identifier] and [secret]
+/// to obtain an authorization token from the authorization server, instead
+/// of sending a user through a dedicated flow.
+///
+/// The client [identifier] and [secret] are required, and are
+/// used to identify and authenticate your specific OAuth2 client. These are
+/// usually global to the program using this library.
+///
+/// The specific permissions being requested from the authorization server may
+/// be specified via [scopes]. The scope strings are specific to the
+/// authorization server and may be found in its documentation. Note that you
+/// may not be granted access to every scope you request; you may check the
+/// [Credentials.scopes] field of [Client.credentials] to see which scopes you
+/// were granted.
+///
+/// The scope strings will be separated by the provided [delimiter]. This
+/// defaults to `" "`, the OAuth2 standard, but some APIs (such as Facebook's)
+/// use non-standard delimiters.
+///
+/// By default, this follows the OAuth2 spec and requires the server's responses
+/// to be in JSON format. However, some servers return non-standard response
+/// formats, which can be parsed using the [getParameters] function.
+///
+/// This function is passed the `Content-Type` header of the response as well as
+/// its body as a UTF-8-decoded string. It should return a map in the same
+/// format as the [standard JSON response](https://tools.ietf.org/html/rfc6749#section-5.1)
+Future<Client> clientCredentialsGrant(
+    Uri authorizationEndpoint, String identifier, String secret,
+    {Iterable<String> scopes,
+    bool basicAuth = true,
+    http.Client httpClient,
+    String delimiter,
+    Map<String, dynamic> getParameters(
+        MediaType contentType, String body)}) async {
+  delimiter ??= ' ';
+  var startTime = new DateTime.now();
+
+  var body = {"grant_type": "client_credentials"};
+
+  var headers = <String, String>{};
+
+  if (identifier != null) {
+    if (basicAuth) {
+      headers['Authorization'] = basicAuthHeader(identifier, secret);
+    } else {
+      body['client_id'] = identifier;
+      if (secret != null) body['client_secret'] = secret;
+    }
+  }
+
+  if (scopes != null && scopes.isNotEmpty)
+    body['scope'] = scopes.join(delimiter);
+
+  if (httpClient == null) httpClient = new http.Client();
+  var response = await httpClient.post(authorizationEndpoint,
+      headers: headers, body: body);
+
+  var credentials = await handleAccessTokenResponse(
+      response, authorizationEndpoint, startTime, scopes, delimiter,
+      getParameters: getParameters);
+  return new Client(credentials,
+      identifier: identifier, secret: secret, httpClient: httpClient);
+}
diff --git a/lib/src/resource_owner_password_grant.dart b/lib/src/resource_owner_password_grant.dart
index a3c16f5..174a3c0 100644
--- a/lib/src/resource_owner_password_grant.dart
+++ b/lib/src/resource_owner_password_grant.dart
@@ -12,7 +12,7 @@
 import 'utils.dart';
 import 'credentials.dart';
 
-/// Obtains credentials using a [resource owner password grant][].
+/// Obtains credentials using a [resource owner password grant](https://tools.ietf.org/html/rfc6749#section-1.3.3).
 ///
 /// This mode of authorization uses the user's username and password to obtain
 /// an authentication token, which can then be stored. This is safer than
diff --git a/test/client_credentials_grant_test.dart b/test/client_credentials_grant_test.dart
new file mode 100644
index 0000000..062414d
--- /dev/null
+++ b/test/client_credentials_grant_test.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@TestOn("vm")
+
+import 'dart:convert';
+
+import 'package:http/http.dart' as http;
+import 'package:oauth2/oauth2.dart' as oauth2;
+import 'package:test/test.dart';
+
+import 'utils.dart';
+
+final success = jsonEncode({
+  "access_token": "2YotnFZFEjr1zCsicMWpAA",
+  "token_type": "bearer",
+  "expires_in": 3600,
+  "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
+});
+
+var auth = 'Basic Y2xpZW50OnNlY3JldA==';
+var authEndpoint = Uri.parse('https://example.com');
+
+void main() {
+  var expectClient;
+  setUp(() => expectClient = new ExpectClient());
+
+  group('basic', () {
+    test('builds correct request with client when using basic auth for client',
+        () async {
+      expectClient.expectRequest((request) async {
+        expect(auth, equals(request.headers['authorization']));
+        expect(request.bodyFields['grant_type'], equals('client_credentials'));
+        return new http.Response(success, 200,
+            headers: {'content-type': 'application/json'});
+      });
+
+      var client = await oauth2.clientCredentialsGrant(
+          authEndpoint, 'client', 'secret',
+          httpClient: expectClient);
+
+      expect(client.credentials, isNotNull);
+      expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+    });
+
+    test('builds correct request when using query parameters for client',
+        () async {
+      expectClient.expectRequest((request) async {
+        expect(request.bodyFields['grant_type'], equals('client_credentials'));
+        expect(request.bodyFields['client_id'], equals('client'));
+        expect(request.bodyFields['client_secret'], equals('secret'));
+        return new http.Response(success, 200,
+            headers: {'content-type': 'application/json'});
+      });
+
+      var client = await oauth2.clientCredentialsGrant(
+          authEndpoint, 'client', 'secret',
+          basicAuth: false, httpClient: expectClient);
+      expect(client.credentials, isNotNull);
+      expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+    });
+
+    test('builds correct request using scope', () async {
+      expectClient.expectRequest((request) async {
+        expect(auth, equals(request.headers['authorization']));
+        expect(request.bodyFields['grant_type'], equals('client_credentials'));
+        expect(request.bodyFields['scope'], equals('one two'));
+        return new http.Response(success, 200,
+            headers: {'content-type': 'application/json'});
+      });
+
+      var client = await oauth2.clientCredentialsGrant(
+          authEndpoint, 'client', 'secret',
+          scopes: ['one', 'two'], httpClient: expectClient);
+      expect(client.credentials, isNotNull);
+      expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+    });
+
+    test('builds correct request using scope with custom delimiter', () async {
+      expectClient.expectRequest((request) async {
+        expect(request.bodyFields['grant_type'], equals('client_credentials'));
+        expect(request.bodyFields['scope'], equals('one,two'));
+        return new http.Response(success, 200,
+            headers: {'content-type': 'application/json'});
+      });
+
+      await oauth2.clientCredentialsGrant(authEndpoint, 'client', 'secret',
+          scopes: ['one', 'two'], httpClient: expectClient, delimiter: ',');
+    });
+
+    test('merges with existing query parameters', () async {
+      var authEndpoint = Uri.parse('https://example.com?query=value');
+
+      expectClient.expectRequest((request) async {
+        expect(request.bodyFields['grant_type'], equals('client_credentials'));
+        expect(request.bodyFields['client_id'], equals('client'));
+        expect(request.bodyFields['client_secret'], equals('secret'));
+        expect(request.url.queryParameters['query'], equals('value'));
+        return new http.Response(success, 200,
+            headers: {'content-type': 'application/json'});
+      });
+
+      var client = await oauth2.clientCredentialsGrant(
+          authEndpoint, 'client', 'secret',
+          basicAuth: false, httpClient: expectClient);
+      expect(client.credentials, isNotNull);
+      expect(client.credentials.accessToken, equals('2YotnFZFEjr1zCsicMWpAA'));
+    });
+  });
+}