Skip to content

Commit 587351a

Browse files
author
Renato Marinho
authored
Merge pull request #271 from Te7a-Houdini/feature-bitbucket
Bitbucket Integration
2 parents aafe5bc + 1036da8 commit 587351a

File tree

11 files changed

+426
-9
lines changed

11 files changed

+426
-9
lines changed

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ GITLAB_KEY=
1313
GITLAB_SECRET=
1414
GITLAB_INSTANCE_URI=https://gitlab.com/
1515

16+
BITBUCKET_CLIENT_ID=
17+
BITBUCKET_CLIENT_SECRET=
18+
BITBUCKET_INSTANCE_URI=https://api.bitbucket.org
19+
1620
DB_CONNECTION=mysql
1721
DB_HOST=
1822
DB_PORT=3306

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Laravel GitScrum in gitter.im : [https://gitter.im/laravel-gitscrum/Lobby](https
5353

5454
### Features
5555

56-
GitScrum can be integrated with **Github** or **Gitlab**.
56+
GitScrum can be integrated with **Github** or **Gitlab** or **Bitbucket**.
5757

5858
- **Product Backlog** contains the Product Owner's assessment of business value
5959

@@ -196,6 +196,25 @@ GITLAB_SECRET=XXXXXXXXXXXXXXXXXX
196196
GITLAB_INSTANCE_URI=https://gitlab.com/
197197
```
198198

199+
#### Bitbucket
200+
201+
You must create a new Bitbucket OAuth Consumer, visit [Bitbucket new consumer guide](https://confluence.atlassian.com/bitbucket/integrate-another-application-through-oauth-372605388.html), and make sure you give write permissions when creating the consumer specially on (repositories , issues)
202+
203+
```
204+
name: gitscrum
205+
Callback URL: http://{URL is the SAME APP_URL}/auth/provider/bitbucket/callback
206+
URL: http://{URL is the SAME APP_URL}
207+
Uncheck (This is a private consumer)
208+
```
209+
210+
.env file
211+
212+
```
213+
BITBUCKET_CLIENT_ID=XXXXX -> Bitbucket Key
214+
BITBUCKET_CLIENT_SECRET=XXXXXXXXXXXXXXXXXX Bitbucket Secret
215+
BITBUCKET_INSTANCE_URI=https://api.bitbucket.org
216+
```
217+
199218
#### Proxy
200219

201220
.env file

app/Classes/Bitbucket.php

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
<?php
2+
/**
3+
* Laravel GitScrum <https://github.com/GitScrum-Community/laravel-gitscrum>
4+
*
5+
* The MIT License (MIT)
6+
* Copyright (c) 2017 Renato Marinho <renato.marinho@s2move.com>
7+
*/
8+
9+
namespace GitScrum\Classes;
10+
11+
use Auth;
12+
use Carbon\Carbon;
13+
use GitScrum\Models\User;
14+
use GitScrum\Models\Issue;
15+
use GitScrum\Models\Organization;
16+
use GitScrum\Models\ProductBacklog;
17+
use GitScrum\Models\Branch;
18+
use GitScrum\Contracts\ProviderInterface;
19+
use GuzzleHttp\Client as GuzzleClient;
20+
21+
class Bitbucket implements ProviderInterface
22+
{
23+
24+
public function tplUser($obj)
25+
{
26+
return [
27+
'provider_id' => $obj->id,
28+
'provider' => 'bitbucket',
29+
'username' => $obj->nickname,
30+
'name' => $obj->name,
31+
'token' => $obj->token,
32+
'refresh_token' => $obj->refreshToken,
33+
'avatar' => $obj->avatar,
34+
'html_url' => $obj->user['links']['html']['href'],
35+
'since' => Carbon::parse($obj->user['created_on'])->toDateTimeString(),
36+
'location' => $obj->user['location'],
37+
'blog' => $obj->user['website'],
38+
'email' => $obj->email,
39+
];
40+
}
41+
42+
public function tplRepository($repo, $slug = false)
43+
{
44+
$organization = $this->organization($repo);
45+
46+
if (!$organization) {
47+
return;
48+
}
49+
50+
return (object) [
51+
'provider_id' => $repo->uuid,
52+
'organization_id' => $organization->id,
53+
'organization_title' => $organization->username,
54+
'slug' => $repo->slug,
55+
'title' => $repo->slug,
56+
'fullname' => $repo->name,
57+
'is_private' => $repo->is_private,
58+
'html_url' => $repo->links->html->href,
59+
'description' => null,
60+
'fork' => $repo->fork_policy =='allow_forks' ? true : false,
61+
'url' => $repo->links->self->href,
62+
'since' => Carbon::parse($repo->created_on)->toDateTimeString(),
63+
'pushed_at' => Carbon::parse($repo->updated_on)->toDateTimeString(),
64+
'ssh_url' => $repo->links->clone[1]->href,
65+
'clone_url' => $repo->links->clone[0]->href,
66+
'homepage' => $repo->links->html->href,
67+
'default_branch' => isset($repo->mainbranch) ? (isset($repo->mainbranch->name) ? $repo->mainbranch->name : $repo->mainbranch ) : null,
68+
];
69+
}
70+
71+
public function tplIssue($obj, $productBacklogId)
72+
{
73+
if (isset($obj->assignee->username)) {
74+
$user = User::where('username', $obj->assignee->username)
75+
->where('provider', 'bitbucket')->first();
76+
}
77+
78+
return [
79+
'provider_id' => $obj->repository->uuid,
80+
'user_id' => isset($user->id) ? $user->id : Auth::user()->id,
81+
'product_backlog_id' => $productBacklogId,
82+
'effort' => 0,
83+
'config_issue_effort_id' => 1,
84+
'issue_type_id' => 1,
85+
'number' => $obj->id,
86+
'title' => $obj->title,
87+
'description' => $obj->content->raw,
88+
'state' => $obj->state,
89+
'html_url' => $obj->content->html,
90+
'created_at' => Carbon::parse($obj->created_on)->toDateTimeString(),
91+
'updated_at' => Carbon::parse($obj->created_on)->toDateTimeString(),
92+
];
93+
}
94+
95+
public function tplOrganization($obj)
96+
{
97+
return [
98+
'provider_id' => $obj->uuid,
99+
'username' => $obj->username,
100+
'url' => $obj->links->self->href ,
101+
'repos_url' => $obj->links->repositories->href,
102+
'events_url' => null,
103+
'hooks_url' => $obj->links->hooks->href,
104+
'issues_url' => null,
105+
'members_url' => null,
106+
'public_members_url' => null,
107+
'avatar_url' => $obj->links->avatar->href,
108+
'description' => null,
109+
'title' => $obj->display_name,
110+
'blog' => $obj->website,
111+
'location' => $obj->location,
112+
'email' => null,
113+
'public_repos' => null,
114+
'html_url' => $obj->links->html->href,
115+
'total_private_repos' => null,
116+
'since' => Carbon::parse($obj->created_on)->toDateTimeString(),
117+
'disk_usage' => null,
118+
'provider' => 'bitbucket'
119+
];
120+
}
121+
122+
public function readRepositories($page = 1, &$repos = null)
123+
{
124+
$url = env('BITBUCKET_INSTANCE_URI').'/2.0/repositories?token='.Auth::user()->token.'&role=contributor&pagelen=20';
125+
126+
$repos = $this->assertTokenNotExpired(Helper::request($url),$url);
127+
128+
$response = collect($repos->values)->map(function ($repo) {
129+
return $this->tplRepository($repo);
130+
});
131+
132+
return $response;
133+
}
134+
135+
public function createOrUpdateRepository($owner, $obj, $oldTitle = null)
136+
{
137+
}
138+
139+
public function organization($obj)
140+
{
141+
if (!isset($obj->owner)) {
142+
return false;
143+
}
144+
145+
$endPoint = strtolower($obj->owner->type) == 'user' ? 'users' : 'teams';
146+
147+
$url = env('BITBUCKET_INSTANCE_URI').'/2.0/'.$endPoint.'/' .$obj->owner->username ;
148+
149+
$response = (object) $this->assertTokenNotExpired(Helper::request($url),$url);
150+
151+
$data = $this->tplOrganization($response);
152+
153+
$organization = Organization::updateOrCreate(['provider_id' => $data['provider_id']],$data);
154+
155+
$organization->users()->sync([Auth::id()]);
156+
157+
return $organization;
158+
}
159+
160+
161+
/**
162+
* Retrieves all repo collaborators in case the owner is logged in
163+
*
164+
* @param $owner
165+
* @param $repo
166+
* @param null $providerId
167+
*/
168+
public function readCollaborators($owner, $repo, $providerId = null)
169+
{
170+
$url = env('BITBUCKET_INSTANCE_URI').'/1.0/privileges/'.$owner .'/'.$repo ;
171+
172+
$collaborators = $this->assertTokenNotExpired(Helper::request($url),$url);
173+
174+
if (is_null($collaborators))
175+
{
176+
return ;
177+
}
178+
179+
foreach ($collaborators as $collaborator)
180+
{
181+
$user = User::where('provider','bitbucket')->where('username',$collaborator->user->username)->first();
182+
183+
if (is_null($user))
184+
{
185+
$url = env('BITBUCKET_INSTANCE_URI').'/2.0/users/' .$collaborator->user->username;
186+
187+
$user = $this->assertTokenNotExpired(Helper::request($url),$url);
188+
189+
$data = [
190+
'provider_id' => $user->uuid,
191+
'provider' => 'bitbucket',
192+
'username' => $user->username,
193+
'name' => $user->display_name,
194+
'avatar' => $user->links->avatar->href,
195+
'html_url' => $user->links->html->href,
196+
'since' => Carbon::parse($user->created_on)->toDateTimeString(),
197+
'location' => $user->location,
198+
'blog' => $user->website,
199+
];
200+
201+
$user = User::create($data);
202+
}
203+
204+
$userId[] = $user->id;
205+
}
206+
207+
$organization = Organization::where('username', $owner)
208+
->where('provider', 'bitbucket')->first()->users();
209+
210+
$organization->syncWithoutDetaching($userId);
211+
}
212+
213+
public function createBranches($owner, $product_backlog_id, $repo, $providerId = null)
214+
{
215+
$branches = collect(Helper::request(env('BITBUCKET_INSTANCE_URI').'/1.0/repositories/'.$owner . '/' . $repo .'/branches'));
216+
217+
$branchesData = [];
218+
foreach ($branches as $branchName => $branchData) {
219+
220+
$branchesData[] = [
221+
'product_backlog_id' => $product_backlog_id,
222+
'title' => $branchName,
223+
'sha' => $branchData->raw_node,
224+
'created_at' => Carbon::now(),
225+
'updated_at' => Carbon::now(),
226+
];
227+
}
228+
229+
if ($branchesData) {
230+
Branch::insert($branchesData);
231+
}
232+
}
233+
234+
public function readIssues()
235+
{
236+
$repos = ProductBacklog::with('organization')->get();
237+
238+
foreach ($repos as $repo)
239+
{
240+
$url = env('BITBUCKET_INSTANCE_URI').'/2.0/repositories/'.$repo->organization->username.'/' . $repo->title .'/issues' ;
241+
242+
$issues = $this->assertTokenNotExpired(Helper::request($url),$url);
243+
244+
foreach ($issues->values as $issue)
245+
{
246+
$data = $this->tplIssue($issue, $repo->id);
247+
248+
if (!Issue::where('provider_id', $data['provider_id'])->where('number',$data['number'])->where('provider', 'bitbucket')->first())
249+
{
250+
Issue::create($data)->users()->sync([$data['user_id']]);
251+
}
252+
}
253+
}
254+
255+
}
256+
257+
258+
public function createOrUpdateIssue($obj)
259+
{
260+
}
261+
262+
public function createOrUpdateIssueComment($obj, $verb = 'POST')
263+
{
264+
}
265+
266+
public function deleteIssueComment($obj)
267+
{
268+
}
269+
270+
/**
271+
* will call the refresh token method in case the token expired
272+
* @param $obj
273+
* @param $url
274+
* @return mixed
275+
*/
276+
private function assertTokenNotExpired($obj,$url)
277+
{
278+
if (isset($obj->type) && $obj->type == 'error')
279+
{
280+
return $this->refreshToken($url);
281+
}
282+
283+
return $obj;
284+
}
285+
286+
/**
287+
* it will refresh the user token and then retry the failed request
288+
* @param $failedUrlRequest
289+
* @return mixed
290+
*/
291+
private function refreshToken ($failedUrlRequest)
292+
{
293+
$options = [
294+
'auth' => [config('services.bitbucket.client_id'), config('services.bitbucket.client_secret')],
295+
'form_params' => [
296+
'grant_type' => 'refresh_token',
297+
'refresh_token' => Auth::user()->refresh_token
298+
]
299+
];
300+
301+
$response = (new GuzzleClient)
302+
->post('https://bitbucket.org/site/oauth2/access_token', $options)
303+
->getBody()->getContents();
304+
305+
$response = json_decode($response, true);
306+
307+
Auth::user()->update([
308+
'token' => $response['access_token'],
309+
'refresh_token' => $response['refresh_token']
310+
]);
311+
312+
return Helper::request($failedUrlRequest);
313+
}
314+
}

app/Classes/Helper.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,16 @@ public static function request($url, $auth = true, $customRequest = null, $postF
119119
'Content-Length: '.strlen($postFields), ]);
120120
}
121121

122-
//curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer OAUTH-TOKEN']);
122+
if (strtolower($user->provider) == 'bitbucket')
123+
{
124+
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer '.$user->token]);
125+
}
123126

124127
if (!is_null($customRequest)) {
125128
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $customRequest); //'PATCH'
126129
}
127130

128-
if ($auth && isset($user->username)) {
131+
if ($auth && isset($user->username) && strtolower($user->provider) != 'bitbucket') {
129132
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
130133
curl_setopt($ch, CURLOPT_USERPWD, $user->username.':'.$user->token);
131134
}

0 commit comments

Comments
 (0)