Skip to content

Commit d0d6f44

Browse files
authored
Merge pull request grafana#11314 from BenoitKnecht/gitlab-integration
GitLab authentication backend
2 parents 570d2fe + 189de87 commit d0d6f44

File tree

7 files changed

+268
-2
lines changed

7 files changed

+268
-2
lines changed

conf/defaults.ini

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,18 @@ api_url = https://api.github.com/user
270270
team_ids =
271271
allowed_organizations =
272272

273+
#################################### GitLab Auth #########################
274+
[auth.gitlab]
275+
enabled = false
276+
allow_sign_up = true
277+
client_id = some_id
278+
client_secret = some_secret
279+
scopes = api
280+
auth_url = https://gitlab.com/oauth/authorize
281+
token_url = https://gitlab.com/oauth/token
282+
api_url = https://gitlab.com/api/v4
283+
allowed_groups =
284+
273285
#################################### Google Auth #########################
274286
[auth.google]
275287
enabled = false

docs/sources/installation/configuration.md

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ command line in the init.d script or the systemd service file.
8484

8585
### temp_data_lifetime
8686

87-
How long temporary images in `data` directory should be kept. Defaults to: `24h`. Supported modifiers: `h` (hours),
87+
How long temporary images in `data` directory should be kept. Defaults to: `24h`. Supported modifiers: `h` (hours),
8888
`m` (minutes), for example: `168h`, `30m`, `10h30m`. Use `0` to never clean up temporary files.
8989

9090
### logs
@@ -430,6 +430,108 @@ allowed_organizations = github google
430430

431431
<hr>
432432

433+
## [auth.gitlab]
434+
435+
> Only available in Grafana v5.3+.
436+
437+
You need to [create a GitLab OAuth
438+
application](https://docs.gitlab.com/ce/integration/oauth_provider.html).
439+
Choose a descriptive *Name*, and use the following *Redirect URI*:
440+
441+
```
442+
https://grafana.example.com/login/gitlab
443+
```
444+
445+
where `https://grafana.example.com` is the URL you use to connect to Grafana.
446+
Adjust it as needed if you don't use HTTPS or if you use a different port; for
447+
instance, if you access Grafana at `http://203.0.113.31:3000`, you should use
448+
449+
```
450+
http://203.0.113.31:3000/login/gitlab
451+
```
452+
453+
Finally, select *api* as the *Scope* and submit the form. Note that if you're
454+
not going to use GitLab groups for authorization (i.e. not setting
455+
`allowed_groups`, see below), you can select *read_user* instead of *api* as
456+
the *Scope*, thus giving a more restricted access to your GitLab API.
457+
458+
You'll get an *Application Id* and a *Secret* in return; we'll call them
459+
`GITLAB_APPLICATION_ID` and `GITLAB_SECRET` respectively for the rest of this
460+
section.
461+
462+
Add the following to your Grafana configuration file to enable GitLab
463+
authentication:
464+
465+
```ini
466+
[auth.gitlab]
467+
enabled = false
468+
allow_sign_up = false
469+
client_id = GITLAB_APPLICATION_ID
470+
client_secret = GITLAB_SECRET
471+
scopes = api
472+
auth_url = https://gitlab.com/oauth/authorize
473+
token_url = https://gitlab.com/oauth/token
474+
api_url = https://gitlab.com/api/v4
475+
allowed_groups =
476+
```
477+
478+
Restart the Grafana backend for your changes to take effect.
479+
480+
If you use your own instance of GitLab instead of `gitlab.com`, adjust
481+
`auth_url`, `token_url` and `api_url` accordingly by replacing the `gitlab.com`
482+
hostname with your own.
483+
484+
With `allow_sign_up` set to `false`, only existing users will be able to login
485+
using their GitLab account, but with `allow_sign_up` set to `true`, *any* user
486+
who can authenticate on GitLab will be able to login on your Grafana instance;
487+
if you use the public `gitlab.com`, it means anyone in the world would be able
488+
to login on your Grafana instance.
489+
490+
You can can however limit access to only members of a given group or list of
491+
groups by setting the `allowed_groups` option.
492+
493+
### allowed_groups
494+
495+
To limit access to authenticated users that are members of one or more [GitLab
496+
groups](https://docs.gitlab.com/ce/user/group/index.html), set `allowed_groups`
497+
to a comma- or space-separated list of groups. For instance, if you want to
498+
only give access to members of the `example` group, set
499+
500+
501+
```ini
502+
allowed_groups = example
503+
```
504+
505+
If you want to also give access to members of the subgroup `bar`, which is in
506+
the group `foo`, set
507+
508+
```ini
509+
allowed_groups = example, foo/bar
510+
```
511+
512+
Note that in GitLab, the group or subgroup name doesn't always match its
513+
display name, especially if the display name contains spaces or special
514+
characters. Make sure you always use the group or subgroup name as it appears
515+
in the URL of the group or subgroup.
516+
517+
Here's a complete example with `alloed_sign_up` enabled, and access limited to
518+
the `example` and `foo/bar` groups:
519+
520+
```ini
521+
[auth.gitlab]
522+
enabled = false
523+
allow_sign_up = true
524+
client_id = GITLAB_APPLICATION_ID
525+
client_secret = GITLAB_SECRET
526+
scopes = api
527+
auth_url = https://gitlab.com/oauth/authorize
528+
token_url = https://gitlab.com/oauth/token
529+
api_url = https://gitlab.com/api/v4
530+
allowed_groups = example, foo/bar
531+
```
532+
533+
<hr>
534+
433535
## [auth.google]
434536

435537
First, you need to create a Google OAuth Client:

pkg/models/models.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ const (
88
TWITTER
99
GENERIC
1010
GRAFANA_COM
11+
GITLAB
1112
)

pkg/social/gitlab_oauth.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package social
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"regexp"
8+
9+
"github.com/grafana/grafana/pkg/models"
10+
11+
"golang.org/x/oauth2"
12+
)
13+
14+
type SocialGitlab struct {
15+
*SocialBase
16+
allowedDomains []string
17+
allowedGroups []string
18+
apiUrl string
19+
allowSignup bool
20+
}
21+
22+
var (
23+
ErrMissingGroupMembership = &Error{"User not a member of one of the required groups"}
24+
)
25+
26+
func (s *SocialGitlab) Type() int {
27+
return int(models.GITLAB)
28+
}
29+
30+
func (s *SocialGitlab) IsEmailAllowed(email string) bool {
31+
return isEmailAllowed(email, s.allowedDomains)
32+
}
33+
34+
func (s *SocialGitlab) IsSignupAllowed() bool {
35+
return s.allowSignup
36+
}
37+
38+
func (s *SocialGitlab) IsGroupMember(client *http.Client) bool {
39+
if len(s.allowedGroups) == 0 {
40+
return true
41+
}
42+
43+
for groups, url := s.GetGroups(client, s.apiUrl+"/groups"); groups != nil; groups, url = s.GetGroups(client, url) {
44+
for _, allowedGroup := range s.allowedGroups {
45+
for _, group := range groups {
46+
if group == allowedGroup {
47+
return true
48+
}
49+
}
50+
}
51+
}
52+
53+
return false
54+
}
55+
56+
func (s *SocialGitlab) GetGroups(client *http.Client, url string) ([]string, string) {
57+
type Group struct {
58+
FullPath string `json:"full_path"`
59+
}
60+
61+
var (
62+
groups []Group
63+
next string
64+
)
65+
66+
if url == "" {
67+
return nil, next
68+
}
69+
70+
response, err := HttpGet(client, url)
71+
if err != nil {
72+
s.log.Error("Error getting groups from GitLab API", "err", err)
73+
return nil, next
74+
}
75+
76+
if err := json.Unmarshal(response.Body, &groups); err != nil {
77+
s.log.Error("Error parsing JSON from GitLab API", "err", err)
78+
return nil, next
79+
}
80+
81+
fullPaths := make([]string, len(groups))
82+
for i, group := range groups {
83+
fullPaths[i] = group.FullPath
84+
}
85+
86+
if link, ok := response.Headers["Link"]; ok {
87+
pattern := regexp.MustCompile(`<([^>]+)>; rel="next"`)
88+
if matches := pattern.FindStringSubmatch(link[0]); matches != nil {
89+
next = matches[1]
90+
}
91+
}
92+
93+
return fullPaths, next
94+
}
95+
96+
func (s *SocialGitlab) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
97+
98+
var data struct {
99+
Id int
100+
Username string
101+
Email string
102+
Name string
103+
State string
104+
}
105+
106+
response, err := HttpGet(client, s.apiUrl+"/user")
107+
if err != nil {
108+
return nil, fmt.Errorf("Error getting user info: %s", err)
109+
}
110+
111+
err = json.Unmarshal(response.Body, &data)
112+
if err != nil {
113+
return nil, fmt.Errorf("Error getting user info: %s", err)
114+
}
115+
116+
if data.State != "active" {
117+
return nil, fmt.Errorf("User %s is inactive", data.Username)
118+
}
119+
120+
userInfo := &BasicUserInfo{
121+
Id: fmt.Sprintf("%d", data.Id),
122+
Name: data.Name,
123+
Login: data.Username,
124+
Email: data.Email,
125+
}
126+
127+
if !s.IsGroupMember(client) {
128+
return nil, ErrMissingGroupMembership
129+
}
130+
131+
return userInfo, nil
132+
}

pkg/social/social.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func NewOAuthService() {
5555
setting.OAuthService = &setting.OAuther{}
5656
setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo)
5757

58-
allOauthes := []string{"github", "google", "generic_oauth", "grafananet", "grafana_com"}
58+
allOauthes := []string{"github", "gitlab", "google", "generic_oauth", "grafananet", "grafana_com"}
5959

6060
for _, name := range allOauthes {
6161
sec := setting.Raw.Section("auth." + name)
@@ -115,6 +115,20 @@ func NewOAuthService() {
115115
}
116116
}
117117

118+
// GitLab.
119+
if name == "gitlab" {
120+
SocialMap["gitlab"] = &SocialGitlab{
121+
SocialBase: &SocialBase{
122+
Config: &config,
123+
log: logger,
124+
},
125+
allowedDomains: info.AllowedDomains,
126+
apiUrl: info.ApiUrl,
127+
allowSignup: info.AllowSignup,
128+
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
129+
}
130+
}
131+
118132
// Google.
119133
if name == "google" {
120134
SocialMap["google"] = &SocialGoogle{

public/app/partials/login.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
5151
<i class="btn-service-icon fa fa-github"></i>
5252
Sign in with GitHub
5353
</a>
54+
<a class="btn btn-medium btn-service btn-service--gitlab login-btn" href="login/gitlab" target="_self" ng-if="oauth.gitlab">
55+
<i class="btn-service-icon fa fa-gitlab"></i>
56+
Sign in with GitLab
57+
</a>
5458
<a class="btn btn-medium btn-inverse btn-service btn-service--grafanacom login-btn" href="login/grafana_com" target="_self"
5559
ng-if="oauth.grafana_com">
5660
<i class="btn-service-icon"></i>

public/sass/_variables.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ $tabs-padding: 10px 15px 9px;
195195

196196
$external-services: (
197197
github: (bgColor: #464646, borderColor: #393939, icon: ''),
198+
gitlab: (bgColor: #fc6d26, borderColor: #e24329, icon: ''),
198199
google: (bgColor: #e84d3c, borderColor: #b83e31, icon: ''),
199200
grafanacom: (bgColor: inherit, borderColor: #393939, icon: ''),
200201
oauth: (bgColor: inherit, borderColor: #393939, icon: '')

0 commit comments

Comments
 (0)