Skip to content

Commit 01e9175

Browse files
authored
Merge pull request #29 from zamblauskas/basic-auth-merge
Basic auth support
2 parents d88adbe + 37baffa commit 01e9175

9 files changed

+96
-30
lines changed

build.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Dependencies._
22

33
name := """bitbucket-scala-client"""
44

5-
version := "1.7.1-SNAPSHOT"
5+
version := "1.8.0-SNAPSHOT"
66

77
scalaVersion := "2.10.5"
88

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.codacy.client.bitbucket.client
2+
3+
import play.api.libs.oauth.{ConsumerKey, OAuthCalculator, RequestToken}
4+
import play.api.libs.ws.{WSAuthScheme, WSRequest}
5+
6+
/**
7+
* Handles request authentication.
8+
* Provides several different authentication options.
9+
*
10+
* @author - Robertas Zamblauskas
11+
*/
12+
object Authentication {
13+
14+
sealed trait Credentials
15+
16+
case class OAuthCredentials(key: String, secretKey: String, token: String, secretToken: String) extends Credentials
17+
18+
/**
19+
* Your username and password | app password.
20+
*/
21+
case class BasicAuthCredentials(username: String, password: String) extends Credentials
22+
23+
24+
sealed trait Authenticator {
25+
def authenticate(req: WSRequest): WSRequest
26+
}
27+
28+
object Authenticator {
29+
def fromCredentials(credentials: Credentials): Authenticator = {
30+
credentials match {
31+
case c: OAuthCredentials => new OAuthAuthenticator(c)
32+
case c: BasicAuthCredentials => new BasicAuthAuthenticator(c)
33+
}
34+
}
35+
}
36+
37+
class OAuthAuthenticator(credentials: OAuthCredentials) extends Authenticator {
38+
private lazy val KEY = ConsumerKey(credentials.key, credentials.secretKey)
39+
private lazy val TOKEN = RequestToken(credentials.token, credentials.secretToken)
40+
41+
private lazy val requestSigner = OAuthCalculator(KEY, TOKEN)
42+
43+
def authenticate(req: WSRequest): WSRequest = req.sign(requestSigner)
44+
}
45+
46+
class BasicAuthAuthenticator(credentials: BasicAuthCredentials) extends Authenticator {
47+
def authenticate(req: WSRequest): WSRequest = req.withAuth(credentials.username, credentials.password, WSAuthScheme.BASIC)
48+
}
49+
50+
/**
51+
* Provide nicer syntax for authentication.
52+
*/
53+
implicit class WsRequestExtensions(val req: WSRequest) extends AnyVal {
54+
def authenticate(authenticator: Authenticator): WSRequest = authenticator.authenticate(req)
55+
}
56+
}

src/main/scala/com/codacy/client/bitbucket/client/BitbucketClient.scala

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
package com.codacy.client.bitbucket.client
22

33
import java.net.URI
4-
import java.util.concurrent.{SynchronousQueue, ThreadPoolExecutor, TimeUnit}
54

5+
import com.codacy.client.bitbucket.client.Authentication._
66
import com.codacy.client.bitbucket.util.HTTPStatusCodes
77
import com.codacy.client.bitbucket.util.Implicits.URIQueryParam
88
import com.ning.http.client.AsyncHttpClientConfig
99
import play.api.http.{ContentTypeOf, Writeable}
1010
import play.api.libs.json._
11-
import play.api.libs.oauth._
1211
import play.api.libs.ws.ning.{NingAsyncHttpClientConfigBuilder, NingWSClient}
1312

1413
import scala.concurrent.Await
1514
import scala.concurrent.duration.{Duration, SECONDS}
1615
import scala.util.{Failure, Properties, Success, Try}
1716

18-
class BitbucketClient(key: String, secretKey: String, token: String, secretToken: String) {
1917

20-
private lazy val KEY = ConsumerKey(key, secretKey)
21-
private lazy val TOKEN = RequestToken(token, secretToken)
18+
19+
class BitbucketClient(credentials: Credentials) {
2220

2321
private lazy val requestTimeout = Duration(10, SECONDS)
24-
private lazy val requestSigner = OAuthCalculator(KEY, TOKEN)
22+
23+
private lazy val authenticator = Authenticator.fromCredentials(credentials)
2524

2625
/*
2726
* Does an API request and parses the json output into a class
@@ -75,7 +74,7 @@ class BitbucketClient(key: String, secretKey: String, token: String, secretToken
7574
*/
7675
private def performRequest[D, T](method: String, request: Request[T], values: D)(implicit reader: Reads[T], writer: Writeable[D], contentType: ContentTypeOf[D]): RequestResponse[T] = withClientRequest { client =>
7776
val jpromise = client.url(request.url)
78-
.sign(requestSigner)
77+
.authenticate(authenticator)
7978
.withFollowRedirects(follow = true)
8079
.withMethod(method).withBody(values).execute()
8180
val result = Await.result(jpromise, requestTimeout)
@@ -128,7 +127,7 @@ class BitbucketClient(key: String, secretKey: String, token: String, secretToken
128127
/* copy paste from post ... */
129128
def delete[T](url: String): RequestResponse[Boolean] = withClientRequest { client =>
130129
val jpromise = client.url(url)
131-
.sign(requestSigner)
130+
.authenticate(authenticator)
132131
.withFollowRedirects(follow = true)
133132
.delete()
134133
val result = Await.result(jpromise, requestTimeout)
@@ -144,7 +143,7 @@ class BitbucketClient(key: String, secretKey: String, token: String, secretToken
144143

145144
private def get(url: String): Either[ResponseError, JsValue] = withClientEither { client =>
146145
val jpromise = client.url(url)
147-
.sign(requestSigner)
146+
.authenticate(authenticator)
148147
.withFollowRedirects(follow = true)
149148
.get()
150149
val result = Await.result(jpromise, requestTimeout)

src/main/scala/com/codacy/client/bitbucket/service/BuildStatusServices.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class BuildStatusServices(client: BitbucketClient) {
1111
*
1212
*/
1313
def getBuildStatus(owner: String, repository: String, commit: String, key: String): RequestResponse[BuildStatus] = {
14-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/commit/$commit/statuses/build/$key"
14+
val url = s"https://bitbucket.org/api/2.0/repositories/$owner/$repository/commit/$commit/statuses/build/$key"
1515

1616
client.execute(Request(url, classOf[BuildStatus]))
1717
}
@@ -22,7 +22,7 @@ class BuildStatusServices(client: BitbucketClient) {
2222
*
2323
*/
2424
def createBuildStatus(owner: String, repository: String, commit: String, buildStatus: BuildStatus): RequestResponse[BuildStatus] = {
25-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/commit/$commit/statuses/build"
25+
val url = s"https://bitbucket.org/api/2.0/repositories/$owner/$repository/commit/$commit/statuses/build"
2626

2727
val values = Map("state" -> Seq(buildStatus.state.toString), "key" -> Seq(buildStatus.key),
2828
"name" -> Seq(buildStatus.name), "url" -> Seq(buildStatus.url),
@@ -36,7 +36,7 @@ class BuildStatusServices(client: BitbucketClient) {
3636
*
3737
*/
3838
def updateBuildStatus(owner: String, repository: String, commit: String, buildStatus: BuildStatus): RequestResponse[BuildStatus] = {
39-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/commit/$commit/statuses/build/${buildStatus.key}"
39+
val url = s"https://bitbucket.org/api/2.0/repositories/$owner/$repository/commit/$commit/statuses/build/${buildStatus.key}"
4040

4141
val payload = Json.obj(
4242
"state" -> buildStatus.state,

src/main/scala/com/codacy/client/bitbucket/service/CommitServices.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import play.api.libs.json.{JsNumber, JsObject, JsString}
88
class CommitServices(client: BitbucketClient) {
99

1010
def createComment(author: String, repo: String, commit: String, body: String, file: Option[String] = None, line: Option[Int] = None): RequestResponse[CommitComment] = {
11-
val url = s"https://bitbucket.org/!api/1.0/repositories/$author/$repo/changesets/${CommitHelper.anchor(commit)}/comments"
11+
val url = s"https://bitbucket.org/api/1.0/repositories/$author/$repo/changesets/${CommitHelper.anchor(commit)}/comments"
1212

1313
val params = file.map(filename => "filename" -> JsString(filename)) ++
1414
line.map(lineTo => "line_to" -> JsNumber(lineTo))
@@ -19,7 +19,7 @@ class CommitServices(client: BitbucketClient) {
1919
}
2020

2121
def deleteComment(author: String, repo: String, commit: String, commentId: Long): Unit = {
22-
val url = s"https://bitbucket.org/!api/1.0/repositories/$author/$repo/changesets/${CommitHelper.anchor(commit)}/comments/$commentId"
22+
val url = s"https://bitbucket.org/api/1.0/repositories/$author/$repo/changesets/${CommitHelper.anchor(commit)}/comments/$commentId"
2323

2424
client.delete(url)
2525
}

src/main/scala/com/codacy/client/bitbucket/service/HookServices.scala

+12-1
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,21 @@ class HookServices(client: BitbucketClient) {
1919
"url" -> hookUrl,
2020
"events" -> events
2121
)
22-
2322
client.postJson(Request(servicesUrl, classOf[Webhook]), payload)
2423
}
2524

25+
def update(author: String, repo: String, uuid: String,
26+
active: Boolean, description: String, hookUrl: String, events:Set[String]): RequestResponse[Webhook] = {
27+
val servicesUrl = getServicesUrl(author, repo)
28+
val payload = Json.obj(
29+
"active" -> active,
30+
"description" -> description,
31+
"url" -> hookUrl,
32+
"events" -> events
33+
)
34+
client.putJson(Request(s"$servicesUrl/$uuid", classOf[Webhook]), payload)
35+
}
36+
2637
def delete(author: String, repo: String, uuid: String): RequestResponse[Boolean] = {
2738
val servicesUrl = getServicesUrl(author, repo)
2839
client.delete(s"$servicesUrl/$uuid")

src/main/scala/com/codacy/client/bitbucket/service/PullRequestServices.scala

+8-8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class PullRequestServices(client: BitbucketClient) {
1414
*
1515
*/
1616
def getPullRequests(owner: String, repository: String, states: Seq[String] = Seq("OPEN")): RequestResponse[Seq[PullRequest]] = {
17-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/pullrequests?pagelen=50&state=${states.mkString("&state=")}"
17+
val url = s"https://bitbucket.org/api/2.0/repositories/$owner/$repository/pullrequests?pagelen=50&state=${states.mkString("&state=")}"
1818

1919
client.executePaginated(Request(url, classOf[Seq[PullRequest]]))
2020
}
@@ -24,13 +24,13 @@ class PullRequestServices(client: BitbucketClient) {
2424
*
2525
*/
2626
def getPullRequestCommits(owner: String, repository: String, prId: Long): RequestResponse[Seq[SimpleCommit]] = {
27-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/pullrequests/$prId/commits?pagelen=100"
27+
val url = s"https://bitbucket.org/api/2.0/repositories/$owner/$repository/pullrequests/$prId/commits?pagelen=100"
2828

2929
client.executePaginated(Request(url, classOf[Seq[SimpleCommit]]))
3030
}
3131

3232
def create(owner: String, repository: String, title: String, sourceBranch: String, destinationBranch: String): RequestResponse[JsObject] = {
33-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/pullrequests"
33+
val url = s"https://bitbucket.org/api/2.0/repositories/$owner/$repository/pullrequests"
3434

3535
val payload = Json.obj(
3636
"title" -> title,
@@ -50,22 +50,22 @@ class PullRequestServices(client: BitbucketClient) {
5050
}
5151

5252
def postApprove(owner: String, repository: String, prId: Long): RequestResponse[JsObject] = {
53-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/pullrequests/$prId/approve"
53+
val url = s"https://bitbucket.org/api/2.0/repositories/$owner/$repository/pullrequests/$prId/approve"
5454
client.postJson(Request(url, classOf[JsObject]), JsNull)
5555
}
5656

5757
def deleteApprove(owner: String, repository: String, prId: Long): RequestResponse[Boolean] = {
58-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/pullrequests/$prId/approve"
58+
val url = s"https://bitbucket.org/api/2.0/repositories/$owner/$repository/pullrequests/$prId/approve"
5959
client.delete(url)
6060
}
6161

6262
def merge(owner: String, repository: String, prId: Long): RequestResponse[JsObject] = {
63-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/pullrequests/$prId/merge"
63+
val url = s"https://bitbucket.org/api/2.0/repositories/$owner/$repository/pullrequests/$prId/merge"
6464
client.postJson(Request(url, classOf[JsObject]), JsNull)
6565
}
6666

6767
def decline(owner: String, repository: String, prId: Long): RequestResponse[JsObject] = {
68-
val url = s"https://bitbucket.org/!api/2.0/repositories/$owner/$repository/pullrequests/$prId/decline"
68+
val url = s"https://bitbucket.org/api/2.0/repositories/$owner/$repository/pullrequests/$prId/decline"
6969
client.postJson(Request(url, classOf[JsObject]), JsNull)
7070
}
7171

@@ -95,7 +95,7 @@ class PullRequestServices(client: BitbucketClient) {
9595
}
9696

9797
def listComments(author: String, repo: String, pullRequestId: Int): RequestResponse[Seq[SimplePullRequestComment]] = {
98-
val url = s"https://bitbucket.org/!api/1.0/repositories/$author/$repo/pullrequests/$pullRequestId/comments"
98+
val url = s"https://bitbucket.org/api/1.0/repositories/$author/$repo/pullrequests/$pullRequestId/comments"
9999

100100
client.execute(Request(url, classOf[Seq[SimplePullRequestComment]]))
101101
}

src/main/scala/com/codacy/client/bitbucket/service/RepositoryServices.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,22 @@ class RepositoryServices(client: BitbucketClient) {
1111
* Use this if you're looking for a full list of all of the repositories associated with a user
1212
*/
1313
def getRepositories: RequestResponse[Seq[SimpleRepository]] = {
14-
client.execute(Request(s"https://bitbucket.org/!api/1.0/user/repositories", classOf[Seq[SimpleRepository]]))
14+
client.execute(Request(s"https://bitbucket.org/api/1.0/user/repositories", classOf[Seq[SimpleRepository]]))
1515
}
1616

1717
/*
1818
* Gets the list of the user's repositories. Private repositories only appear on this list
1919
* if the caller is authenticated and is authorized to view the repository.
2020
*/
2121
def getRepositories(username: String): RequestResponse[Seq[Repository]] = {
22-
client.executePaginated(Request(s"https://bitbucket.org/!api/2.0/repositories/$username", classOf[Seq[Repository]]))
22+
client.executePaginated(Request(s"https://bitbucket.org/api/2.0/repositories/$username", classOf[Seq[Repository]]))
2323
}
2424

2525
/*
2626
* Creates a ssh key
2727
*/
2828
def createKey(username: String, repo: String, key: String): RequestResponse[SshKey] = {
29-
val url = s"https://bitbucket.org/!api/1.0/repositories/$username/$repo/deploy-keys"
29+
val url = s"https://bitbucket.org/api/1.0/repositories/$username/$repo/deploy-keys"
3030

3131
val values = Json.obj(
3232
"key" -> key,

src/main/scala/com/codacy/client/bitbucket/service/UserServices.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ class UserServices(client: BitbucketClient) {
1010
* Gets the basic information associated with the token owner account.
1111
*/
1212
def getUser: RequestResponse[User] = {
13-
client.execute(Request("https://bitbucket.org/!api/1.0/user", classOf[User]))
13+
client.execute(Request("https://bitbucket.org/api/1.0/user", classOf[User]))
1414
}
1515

1616
/*
1717
* Gets the basic information associated with an account.
1818
*/
1919
def getUser(username: String): RequestResponse[User] = {
20-
client.execute(Request(s"https://bitbucket.org/!api/1.0/users/$username", classOf[User]))
20+
client.execute(Request(s"https://bitbucket.org/api/1.0/users/$username", classOf[User]))
2121
}
2222

2323
/*
2424
* Creates a ssh key
2525
*/
2626
def createKey(username: String, key: String): RequestResponse[SshKey] = {
27-
val url = s"https://bitbucket.org/!api/1.0/users/$username/ssh-keys"
27+
val url = s"https://bitbucket.org/api/1.0/users/$username/ssh-keys"
2828

2929
val values = Json.obj(
3030
"key" -> key,

0 commit comments

Comments
 (0)