Skip to content

Commit b8dffe4

Browse files
committed
Merge branch '2.x'
* 2.x: Add OutsideCollaborators api Fix: Wrong PHPDoc description feature #913 Add support for SSO errors coming from the API (eiriksm)
2 parents 9eb9ebb + 8de302c commit b8dffe4

File tree

9 files changed

+219
-30
lines changed

9 files changed

+219
-30
lines changed

lib/Github/Api/Organization.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Github\Api\Organization\Hooks;
66
use Github\Api\Organization\Members;
7+
use Github\Api\Organization\OutsideCollaborators;
78
use Github\Api\Organization\Teams;
89

910
/**
@@ -100,6 +101,14 @@ public function teams()
100101
return new Teams($this->client);
101102
}
102103

104+
/**
105+
* @return OutsideCollaborators
106+
*/
107+
public function outsideCollaborators()
108+
{
109+
return new OutsideCollaborators($this->client);
110+
}
111+
103112
/**
104113
* @link http://developer.github.com/v3/issues/#list-issues
105114
*
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace Github\Api\Organization;
4+
5+
use Github\Api\AbstractApi;
6+
7+
/**
8+
* @link https://developer.github.com/v3/orgs/outside_collaborators/
9+
*
10+
* @author Matthieu Calie <matthieu@calie.be>
11+
*/
12+
class OutsideCollaborators extends AbstractApi
13+
{
14+
/**
15+
* @link https://developer.github.com/v3/orgs/outside_collaborators/#list-outside-collaborators-for-an-organization
16+
*
17+
* @param string $organization the organization
18+
* @param array $params
19+
*
20+
* @return array the organizations
21+
*/
22+
public function all($organization, array $params = [])
23+
{
24+
return $this->get('/orgs/'.rawurlencode($organization).'/outside_collaborators', $params);
25+
}
26+
27+
/**
28+
* @link https://developer.github.com/v3/orgs/outside_collaborators/#convert-an-organization-member-to-outside-collaborator
29+
*
30+
* @param string $organization the organization
31+
* @param string $username the github username
32+
*
33+
* @return array
34+
*/
35+
public function convert($organization, $username)
36+
{
37+
return $this->put('/orgs/'.rawurlencode($organization).'/outside_collaborators/'.rawurldecode($username));
38+
}
39+
40+
/**
41+
* @link https://developer.github.com/v3/orgs/outside_collaborators/#remove-outside-collaborator-from-an-organization
42+
*
43+
* @param string $organization the organization
44+
* @param string $username the username
45+
*
46+
* @return array
47+
*/
48+
public function remove($organization, $username)
49+
{
50+
return $this->delete('/orgs/'.rawurlencode($organization).'/outside_collaborators/'.rawurldecode($username));
51+
}
52+
}

lib/Github/Api/Search.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public function commits($q, $sort = null, $order = 'desc')
9797
}
9898

9999
/**
100-
* Search commits by filter (q).
100+
* Search topics by filter (q).
101101
*
102102
* @link https://developer.github.com/v3/search/#search-topics
103103
*

lib/Github/Client.php

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@
1919
/**
2020
* Simple yet very cool PHP GitHub client.
2121
*
22-
* @method Api\CurrentUser currentUser()
23-
* @method Api\CurrentUser me()
24-
* @method Api\Enterprise ent()
25-
* @method Api\Enterprise enterprise()
26-
* @method Api\Miscellaneous\CodeOfConduct codeOfConduct()
27-
* @method Api\Miscellaneous\Emojis emojis()
28-
* @method Api\Miscellaneous\Licenses licenses()
29-
* @method Api\GitData git()
30-
* @method Api\GitData gitData()
31-
* @method Api\Gists gist()
32-
* @method Api\Gists gists()
33-
* @method Api\Miscellaneous\Gitignore gitignore()
22+
* @method Api\CurrentUser currentUser()
23+
* @method Api\CurrentUser me()
24+
* @method Api\Enterprise ent()
25+
* @method Api\Enterprise enterprise()
26+
* @method Api\Miscellaneous\CodeOfConduct codeOfConduct()
27+
* @method Api\Miscellaneous\Emojis emojis()
28+
* @method Api\Miscellaneous\Licenses licenses()
29+
* @method Api\GitData git()
30+
* @method Api\GitData gitData()
31+
* @method Api\Gists gist()
32+
* @method Api\Gists gists()
33+
* @method Api\Miscellaneous\Gitignore gitignore()
3434
* @method Api\Apps apps()
3535
* @method Api\Issue issue()
3636
* @method Api\Issue issues()
@@ -43,23 +43,24 @@
4343
* @method Api\Organization\Projects orgProjects()
4444
* @method Api\Organization\Projects organizationProject()
4545
* @method Api\Organization\Projects organizationProjects()
46-
* @method Api\PullRequest pr()
47-
* @method Api\PullRequest pullRequest()
48-
* @method Api\PullRequest pullRequests()
49-
* @method Api\RateLimit rateLimit()
50-
* @method Api\Repo repo()
51-
* @method Api\Repo repos()
52-
* @method Api\Repo repository()
53-
* @method Api\Repo repositories()
54-
* @method Api\Search search()
55-
* @method Api\Organization\Teams team()
56-
* @method Api\Organization\Teams teams()
57-
* @method Api\User user()
58-
* @method Api\User users()
59-
* @method Api\Authorizations authorization()
60-
* @method Api\Authorizations authorizations()
61-
* @method Api\Meta meta()
62-
* @method Api\GraphQL graphql()
46+
* @method Api\Organization\OutsideCollaborators outsideCollaborators()
47+
* @method Api\PullRequest pr()
48+
* @method Api\PullRequest pullRequest()
49+
* @method Api\PullRequest pullRequests()
50+
* @method Api\RateLimit rateLimit()
51+
* @method Api\Repo repo()
52+
* @method Api\Repo repos()
53+
* @method Api\Repo repository()
54+
* @method Api\Repo repositories()
55+
* @method Api\Search search()
56+
* @method Api\Organization\Teams team()
57+
* @method Api\Organization\Teams teams()
58+
* @method Api\User user()
59+
* @method Api\User users()
60+
* @method Api\Authorizations authorization()
61+
* @method Api\Authorizations authorizations()
62+
* @method Api\Meta meta()
63+
* @method Api\GraphQL graphql()
6364
*
6465
* @author Joseph Bielawski <stloyd@gmail.com>
6566
*
@@ -287,6 +288,11 @@ public function api($name)
287288
$api = new Api\GraphQL($this);
288289
break;
289290

291+
case 'outsideCollaborators':
292+
case 'outside_collaborators':
293+
$api = new Api\Organization\OutsideCollaborators($this);
294+
break;
295+
290296
default:
291297
throw new InvalidArgumentException(sprintf('Undefined api instance called: "%s"', $name));
292298
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Github\Exception;
4+
5+
/**
6+
* SsoRequiredException.
7+
*/
8+
class SsoRequiredException extends RuntimeException
9+
{
10+
/** @var string */
11+
private $url;
12+
13+
/**
14+
* @param string $url
15+
* @param int $code
16+
* @param \Throwable|null $previous
17+
*/
18+
public function __construct($url, $code = 0, $previous = null)
19+
{
20+
$this->url = $url;
21+
22+
parent::__construct('Resource protected by organization SAML enforcement. You must grant your personal token access to this organization.', $code, $previous);
23+
}
24+
25+
/**
26+
* @return string
27+
*/
28+
public function getUrl()
29+
{
30+
return $this->url;
31+
}
32+
}

lib/Github/HttpClient/Plugin/GithubExceptionThrower.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Github\Exception\ApiLimitExceedException;
66
use Github\Exception\ErrorException;
77
use Github\Exception\RuntimeException;
8+
use Github\Exception\SsoRequiredException;
89
use Github\Exception\TwoFactorAuthenticationRequiredException;
910
use Github\Exception\ValidationFailedException;
1011
use Github\HttpClient\Message\ResponseMediator;
@@ -101,6 +102,16 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
101102
throw new RuntimeException(implode(', ', $errors), 502);
102103
}
103104

105+
if ((403 === $response->getStatusCode()) && $response->hasHeader('X-GitHub-SSO') && 0 === strpos((string) ResponseMediator::getHeader($response, 'X-GitHub-SSO'), 'required;')) {
106+
// The header will look something like this:
107+
// required; url=https://github.com/orgs/octodocs-test/sso?authorization_request=AZSCKtL4U8yX1H3sCQIVnVgmjmon5fWxks5YrqhJgah0b2tlbl9pZM4EuMz4
108+
// So we strip out the first 14 characters, leaving only the URL.
109+
// @see https://developer.github.com/v3/auth/#authenticating-for-saml-sso
110+
$url = substr((string) ResponseMediator::getHeader($response, 'X-GitHub-SSO'), 14);
111+
112+
throw new SsoRequiredException($url);
113+
}
114+
104115
throw new RuntimeException(isset($content['message']) ? $content['message'] : $content, $response->getStatusCode());
105116
});
106117
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Github\Tests\Api\Organization;
6+
7+
use Github\Tests\Api\TestCase;
8+
9+
class OutsideCollaboratorsTest extends TestCase
10+
{
11+
/**
12+
* @test
13+
*/
14+
public function shouldGetAllRepositoryProjects()
15+
{
16+
$expectedValue = [['login' => 'KnpLabs']];
17+
18+
$api = $this->getApiMock();
19+
$api->expects($this->once())
20+
->method('get')
21+
->with('/orgs/KnpLabs/outside_collaborators')
22+
->will($this->returnValue($expectedValue));
23+
24+
$this->assertEquals($expectedValue, $api->all('KnpLabs'));
25+
}
26+
27+
/**
28+
* @test
29+
*/
30+
public function shouldConvertAnOrganizationMemberToOutsideCollaborator()
31+
{
32+
$expectedValue = 'expectedResponse';
33+
34+
$api = $this->getApiMock();
35+
$api->expects($this->once())
36+
->method('put')
37+
->with('/orgs/KnpLabs/outside_collaborators/username')
38+
->will($this->returnValue($expectedValue));
39+
40+
$this->assertEquals($expectedValue, $api->convert('KnpLabs', 'username'));
41+
}
42+
43+
/**
44+
* @test
45+
*/
46+
public function shouldRemoveAnOutsideCollaboratorFromAnOrganization()
47+
{
48+
$expectedValue = 'expectedResponse';
49+
50+
$api = $this->getApiMock();
51+
$api->expects($this->once())
52+
->method('delete')
53+
->with('/orgs/KnpLabs/outside_collaborators/username')
54+
->will($this->returnValue($expectedValue));
55+
56+
$this->assertEquals($expectedValue, $api->remove('KnpLabs', 'username'));
57+
}
58+
59+
/**
60+
* @return string
61+
*/
62+
protected function getApiClass()
63+
{
64+
return \Github\Api\Organization\OutsideCollaborators::class;
65+
}
66+
}

test/Github/Tests/ClientTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ public function getApiClassesProvider()
196196
['authorizations', Api\Authorizations::class],
197197

198198
['meta', Api\Meta::class],
199+
200+
['outsideCollaborators', Api\Organization\OutsideCollaborators::class],
201+
['outside_collaborators', Api\Organization\OutsideCollaborators::class],
199202
];
200203
}
201204

test/Github/Tests/HttpClient/Plugin/GithubExceptionThrowerTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ public static function responseProvider()
140140
),
141141
'exception' => new \Github\Exception\RuntimeException('Something went wrong with executing your query', 502),
142142
],
143+
'Sso required Response' => [
144+
'response' => new Response(
145+
403,
146+
[
147+
'Content-Type' => 'application/json',
148+
'X-GitHub-SSO' => 'required; url=https://github.com/orgs/octodocs-test/sso?authorization_request=AZSCKtL4U8yX1H3sCQIVnVgmjmon5fWxks5YrqhJgah0b2tlbl9pZM4EuMz4',
149+
]
150+
),
151+
'exception' => new \Github\Exception\SsoRequiredException('https://github.com/orgs/octodocs-test/sso?authorization_request=AZSCKtL4U8yX1H3sCQIVnVgmjmon5fWxks5YrqhJgah0b2tlbl9pZM4EuMz4'),
152+
],
143153
'Default handling' => [
144154
'response' => new Response(
145155
555,

0 commit comments

Comments
 (0)