Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ GITLAB_KEY=
GITLAB_SECRET=
GITLAB_INSTANCE_URI=https://gitlab.com/

BITBUCKET_CLIENT_ID=
BITBUCKET_CLIENT_SECRET=
BITBUCKET_INSTANCE_URI=https://api.bitbucket.org

DB_CONNECTION=mysql
DB_HOST=
DB_PORT=3306
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Laravel GitScrum in gitter.im : [https://gitter.im/laravel-gitscrum/Lobby](https

### Features

GitScrum can be integrated with **Github** or **Gitlab**.
GitScrum can be integrated with **Github** or **Gitlab** or **Bitbucket**.

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

Expand Down Expand Up @@ -195,6 +195,25 @@ GITLAB_SECRET=XXXXXXXXXXXXXXXXXX
GITLAB_INSTANCE_URI=https://gitlab.com/
```

#### Bitbucket

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)

```
name: gitscrum
Callback URL: http://{URL is the SAME APP_URL}/auth/provider/bitbucket/callback
URL: http://{URL is the SAME APP_URL}
Uncheck (This is a private consumer)
```

.env file

```
BITBUCKET_CLIENT_ID=XXXXX -> Bitbucket Key
BITBUCKET_CLIENT_SECRET=XXXXXXXXXXXXXXXXXX Bitbucket Secret
BITBUCKET_INSTANCE_URI=https://api.bitbucket.org
```

#### Proxy

.env file
Expand Down
314 changes: 314 additions & 0 deletions app/Classes/Bitbucket.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
<?php
/**
* Laravel GitScrum <https://github.com/GitScrum-Community/laravel-gitscrum>
*
* The MIT License (MIT)
* Copyright (c) 2017 Renato Marinho <renato.marinho@s2move.com>
*/

namespace GitScrum\Classes;

use Auth;
use Carbon\Carbon;
use GitScrum\Models\User;
use GitScrum\Models\Issue;
use GitScrum\Models\Organization;
use GitScrum\Models\ProductBacklog;
use GitScrum\Models\Branch;
use GitScrum\Contracts\ProviderInterface;
use GuzzleHttp\Client as GuzzleClient;

class Bitbucket implements ProviderInterface
{

public function tplUser($obj)
{
return [
'provider_id' => $obj->id,
'provider' => 'bitbucket',
'username' => $obj->nickname,
'name' => $obj->name,
'token' => $obj->token,
'refresh_token' => $obj->refreshToken,
'avatar' => $obj->avatar,
'html_url' => $obj->user['links']['html']['href'],
'since' => Carbon::parse($obj->user['created_on'])->toDateTimeString(),
'location' => $obj->user['location'],
'blog' => $obj->user['website'],
'email' => $obj->email,
];
}

public function tplRepository($repo, $slug = false)
{
$organization = $this->organization($repo);

if (!$organization) {
return;
}

return (object) [
'provider_id' => $repo->uuid,
'organization_id' => $organization->id,
'organization_title' => $organization->username,
'slug' => $repo->slug,
'title' => $repo->slug,
'fullname' => $repo->name,
'is_private' => $repo->is_private,
'html_url' => $repo->links->html->href,
'description' => null,
'fork' => $repo->fork_policy =='allow_forks' ? true : false,
'url' => $repo->links->self->href,
'since' => Carbon::parse($repo->created_on)->toDateTimeString(),
'pushed_at' => Carbon::parse($repo->updated_on)->toDateTimeString(),
'ssh_url' => $repo->links->clone[1]->href,
'clone_url' => $repo->links->clone[0]->href,
'homepage' => $repo->links->html->href,
'default_branch' => isset($repo->mainbranch) ? (isset($repo->mainbranch->name) ? $repo->mainbranch->name : $repo->mainbranch ) : null,
];
}

public function tplIssue($obj, $productBacklogId)
{
if (isset($obj->assignee->username)) {
$user = User::where('username', $obj->assignee->username)
->where('provider', 'bitbucket')->first();
}

return [
'provider_id' => $obj->repository->uuid,
'user_id' => isset($user->id) ? $user->id : Auth::user()->id,
'product_backlog_id' => $productBacklogId,
'effort' => 0,
'config_issue_effort_id' => 1,
'issue_type_id' => 1,
'number' => $obj->id,
'title' => $obj->title,
'description' => $obj->content->raw,
'state' => $obj->state,
'html_url' => $obj->content->html,
'created_at' => Carbon::parse($obj->created_on)->toDateTimeString(),
'updated_at' => Carbon::parse($obj->created_on)->toDateTimeString(),
];
}

public function tplOrganization($obj)
{
return [
'provider_id' => $obj->uuid,
'username' => $obj->username,
'url' => $obj->links->self->href ,
'repos_url' => $obj->links->repositories->href,
'events_url' => null,
'hooks_url' => $obj->links->hooks->href,
'issues_url' => null,
'members_url' => null,
'public_members_url' => null,
'avatar_url' => $obj->links->avatar->href,
'description' => null,
'title' => $obj->display_name,
'blog' => $obj->website,
'location' => $obj->location,
'email' => null,
'public_repos' => null,
'html_url' => $obj->links->html->href,
'total_private_repos' => null,
'since' => Carbon::parse($obj->created_on)->toDateTimeString(),
'disk_usage' => null,
'provider' => 'bitbucket'
];
}

public function readRepositories($page = 1, &$repos = null)
{
$url = env('BITBUCKET_INSTANCE_URI').'/2.0/repositories?token='.Auth::user()->token.'&role=contributor&pagelen=20';

$repos = $this->assertTokenNotExpired(Helper::request($url),$url);

$response = collect($repos->values)->map(function ($repo) {
return $this->tplRepository($repo);
});

return $response;
}

public function createOrUpdateRepository($owner, $obj, $oldTitle = null)
{
}

public function organization($obj)
{
if (!isset($obj->owner)) {
return false;
}

$endPoint = strtolower($obj->owner->type) == 'user' ? 'users' : 'teams';

$url = env('BITBUCKET_INSTANCE_URI').'/2.0/'.$endPoint.'/' .$obj->owner->username ;

$response = (object) $this->assertTokenNotExpired(Helper::request($url),$url);

$data = $this->tplOrganization($response);

$organization = Organization::updateOrCreate(['provider_id' => $data['provider_id']],$data);

$organization->users()->sync([Auth::id()]);

return $organization;
}


/**
* Retrieves all repo collaborators in case the owner is logged in
*
* @param $owner
* @param $repo
* @param null $providerId
*/
public function readCollaborators($owner, $repo, $providerId = null)
{
$url = env('BITBUCKET_INSTANCE_URI').'/1.0/privileges/'.$owner .'/'.$repo ;

$collaborators = $this->assertTokenNotExpired(Helper::request($url),$url);

if (is_null($collaborators))
{
return ;
}

foreach ($collaborators as $collaborator)
{
$user = User::where('provider','bitbucket')->where('username',$collaborator->user->username)->first();

if (is_null($user))
{
$url = env('BITBUCKET_INSTANCE_URI').'/2.0/users/' .$collaborator->user->username;

$user = $this->assertTokenNotExpired(Helper::request($url),$url);

$data = [
'provider_id' => $user->uuid,
'provider' => 'bitbucket',
'username' => $user->username,
'name' => $user->display_name,
'avatar' => $user->links->avatar->href,
'html_url' => $user->links->html->href,
'since' => Carbon::parse($user->created_on)->toDateTimeString(),
'location' => $user->location,
'blog' => $user->website,
];

$user = User::create($data);
}

$userId[] = $user->id;
}

$organization = Organization::where('username', $owner)
->where('provider', 'bitbucket')->first()->users();

$organization->syncWithoutDetaching($userId);
}

public function createBranches($owner, $product_backlog_id, $repo, $providerId = null)
{
$branches = collect(Helper::request(env('BITBUCKET_INSTANCE_URI').'/1.0/repositories/'.$owner . '/' . $repo .'/branches'));

$branchesData = [];
foreach ($branches as $branchName => $branchData) {

$branchesData[] = [
'product_backlog_id' => $product_backlog_id,
'title' => $branchName,
'sha' => $branchData->raw_node,
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
];
}

if ($branchesData) {
Branch::insert($branchesData);
}
}

public function readIssues()
{
$repos = ProductBacklog::with('organization')->get();

foreach ($repos as $repo)
{
$url = env('BITBUCKET_INSTANCE_URI').'/2.0/repositories/'.$repo->organization->username.'/' . $repo->title .'/issues' ;

$issues = $this->assertTokenNotExpired(Helper::request($url),$url);

foreach ($issues->values as $issue)
{
$data = $this->tplIssue($issue, $repo->id);

if (!Issue::where('provider_id', $data['provider_id'])->where('number',$data['number'])->where('provider', 'bitbucket')->first())
{
Issue::create($data)->users()->sync([$data['user_id']]);
}
}
}

}


public function createOrUpdateIssue($obj)
{
}

public function createOrUpdateIssueComment($obj, $verb = 'POST')
{
}

public function deleteIssueComment($obj)
{
}

/**
* will call the refresh token method in case the token expired
* @param $obj
* @param $url
* @return mixed
*/
private function assertTokenNotExpired($obj,$url)
{
if (isset($obj->type) && $obj->type == 'error')
{
return $this->refreshToken($url);
}

return $obj;
}

/**
* it will refresh the user token and then retry the failed request
* @param $failedUrlRequest
* @return mixed
*/
private function refreshToken ($failedUrlRequest)
{
$options = [
'auth' => [config('services.bitbucket.client_id'), config('services.bitbucket.client_secret')],
'form_params' => [
'grant_type' => 'refresh_token',
'refresh_token' => Auth::user()->refresh_token
]
];

$response = (new GuzzleClient)
->post('https://bitbucket.org/site/oauth2/access_token', $options)
->getBody()->getContents();

$response = json_decode($response, true);

Auth::user()->update([
'token' => $response['access_token'],
'refresh_token' => $response['refresh_token']
]);

return Helper::request($failedUrlRequest);
}
}
7 changes: 5 additions & 2 deletions app/Classes/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,16 @@ public static function request($url, $auth = true, $customRequest = null, $postF
'Content-Length: '.strlen($postFields), ]);
}

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

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

if ($auth && isset($user->username)) {
if ($auth && isset($user->username) && strtolower($user->provider) != 'bitbucket') {
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, $user->username.':'.$user->token);
}
Expand Down
Loading