2022-09-04 09:38:10 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2022-09-13 22:19:43 +00:00
|
|
|
"bytes"
|
2022-09-04 09:38:10 +00:00
|
|
|
"context"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
|
|
|
"flag"
|
2022-09-04 14:36:15 +00:00
|
|
|
"fmt"
|
2022-09-13 22:19:43 +00:00
|
|
|
"io/ioutil"
|
2022-09-04 09:38:10 +00:00
|
|
|
"log"
|
|
|
|
"net/http"
|
2022-09-13 22:19:43 +00:00
|
|
|
"net/url"
|
2022-09-04 09:38:10 +00:00
|
|
|
"os"
|
2022-09-04 14:36:15 +00:00
|
|
|
"time"
|
2022-09-04 09:38:10 +00:00
|
|
|
|
|
|
|
"golang.org/x/oauth2"
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
OAUTH_GITLAB_FILE = ".gitlab-oauth-token"
|
2022-09-04 14:36:15 +00:00
|
|
|
gitlabBaseURL = "https://gitlab.cri.epita.fr"
|
2022-09-04 09:38:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
gitlabClientID = ""
|
|
|
|
gitlabSecret = ""
|
2022-10-17 17:38:17 +00:00
|
|
|
gitlaboauth2Config *oauth2.Config
|
2022-09-04 22:54:33 +00:00
|
|
|
gitlabToken func() *oauth2.Token
|
2022-09-04 09:38:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
flag.StringVar(&gitlabClientID, "gitlab-clientid", gitlabClientID, "ClientID for GitLab's OIDC")
|
|
|
|
flag.StringVar(&gitlabSecret, "gitlab-secret", gitlabSecret, "Secret for GitLab's OIDC")
|
|
|
|
}
|
|
|
|
|
|
|
|
func initializeGitLabOIDC(router *gin.Engine, authrouter *gin.RouterGroup, adminrouter *gin.RouterGroup) {
|
|
|
|
adminrouter.GET("/auth/gitlabcri", redirectOAuth_GitLab)
|
|
|
|
router.GET("/callback/gitlabcri/complete", GitLab_OAuth_complete)
|
|
|
|
|
|
|
|
if gitlabClientID != "" && gitlabSecret != "" {
|
2022-10-17 17:38:17 +00:00
|
|
|
gitlaboauth2Config = &oauth2.Config{
|
2022-09-04 09:38:10 +00:00
|
|
|
ClientID: gitlabClientID,
|
|
|
|
ClientSecret: gitlabSecret,
|
|
|
|
RedirectURL: oidcRedirectURL + baseURL + "/callback/gitlabcri/complete",
|
|
|
|
|
|
|
|
// Discovery returns the OAuth2 endpoints.
|
2022-09-04 14:36:15 +00:00
|
|
|
Endpoint: oauth2.Endpoint{
|
|
|
|
AuthURL: gitlabBaseURL + "/oauth/authorize",
|
|
|
|
TokenURL: gitlabBaseURL + "/oauth/token",
|
|
|
|
},
|
2022-09-04 09:38:10 +00:00
|
|
|
|
|
|
|
// "openid" is a required scope for OpenID Connect flows.
|
|
|
|
Scopes: []string{"api", "read_repository", "email"},
|
|
|
|
}
|
|
|
|
|
2022-09-04 14:36:15 +00:00
|
|
|
authrouter.GET("/api/gitlabcri/repositories", GitLab_GetMyRepositories)
|
2022-09-25 17:25:27 +00:00
|
|
|
|
|
|
|
usersRoutes := authrouter.Group("/api/users/:uid")
|
|
|
|
usersRoutes.Use(userHandler)
|
|
|
|
usersRoutes.Use(sameUserMiddleware)
|
|
|
|
usersRoutes.GET("/gitlabcri/repositories", GitLab_GetMyRepositories)
|
2022-09-04 14:36:15 +00:00
|
|
|
}
|
2022-09-04 09:38:10 +00:00
|
|
|
|
2022-09-04 14:36:15 +00:00
|
|
|
if _, err := os.Stat(OAUTH_GITLAB_FILE); err == nil {
|
2022-09-04 19:44:03 +00:00
|
|
|
tk, err := loadOAuth2Token(OAUTH_GITLAB_FILE)
|
2022-09-04 14:36:15 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Println("Unable to load OAuth2 Token:", err.Error())
|
|
|
|
}
|
2022-09-04 19:44:03 +00:00
|
|
|
|
2022-09-04 22:54:33 +00:00
|
|
|
prepareOAuth2Token(tk)
|
2022-09-04 09:38:10 +00:00
|
|
|
}
|
2022-09-04 14:36:15 +00:00
|
|
|
|
2022-09-04 09:38:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func loadOAuth2Token(file string) (*oauth2.Token, error) {
|
|
|
|
fd, err := os.Open(file)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer fd.Close()
|
|
|
|
|
|
|
|
var tok oauth2.Token
|
|
|
|
err = json.NewDecoder(fd).Decode(&tok)
|
|
|
|
|
|
|
|
return &tok, err
|
|
|
|
}
|
|
|
|
|
2022-09-04 22:54:33 +00:00
|
|
|
func prepareOAuth2Token(tk *oauth2.Token) {
|
|
|
|
tsource := oauth2.ReuseTokenSource(tk, gitlaboauth2Config.TokenSource(context.Background(), tk))
|
|
|
|
|
|
|
|
gitlabToken = func() *oauth2.Token {
|
|
|
|
t, err := tsource.Token()
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Unable to regenerate GitLab token:", err)
|
|
|
|
}
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-04 09:38:10 +00:00
|
|
|
func saveOAuth2Token(file string, tok *oauth2.Token) error {
|
|
|
|
fd, err := os.Create(file)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer fd.Close()
|
|
|
|
|
|
|
|
return json.NewEncoder(fd).Encode(tok)
|
|
|
|
}
|
|
|
|
|
|
|
|
func redirectOAuth_GitLab(c *gin.Context) {
|
|
|
|
session := c.MustGet("Session").(*Session)
|
|
|
|
|
|
|
|
// Save next parameter
|
|
|
|
if len(c.Request.URL.Query().Get("next")) > 0 {
|
|
|
|
session.SetKey("gitlab-oidc-source", c.Request.URL.Query().Get("next"))
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Redirect(http.StatusFound, gitlaboauth2Config.AuthCodeURL(hex.EncodeToString(session.Id)))
|
|
|
|
}
|
|
|
|
|
|
|
|
func GitLab_OAuth_complete(c *gin.Context) {
|
|
|
|
idsession, err := hex.DecodeString(c.Request.URL.Query().Get("state"))
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
session, err := getSession(idsession)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
oauth2Token, err := gitlaboauth2Config.Exchange(c.Request.Context(), c.Request.URL.Query().Get("code"))
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Failed to exchange token: " + err.Error()})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = saveOAuth2Token(OAUTH_GITLAB_FILE, oauth2Token)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to save OAuth2 token: " + err.Error()})
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
2022-09-04 22:54:33 +00:00
|
|
|
prepareOAuth2Token(oauth2Token)
|
2022-09-04 09:38:10 +00:00
|
|
|
log.Println("New GitLab OAuth2 session opened")
|
|
|
|
|
|
|
|
if source, ok := session.GetKey("gitlab-oidc-source"); ok {
|
|
|
|
session.DeleteKey("gitlab-oidc-source")
|
|
|
|
c.Redirect(http.StatusFound, baseURL+source.(string))
|
|
|
|
} else {
|
|
|
|
c.Redirect(http.StatusFound, baseURL+"/works")
|
|
|
|
}
|
|
|
|
session.Update()
|
|
|
|
}
|
|
|
|
|
2022-09-04 14:36:15 +00:00
|
|
|
type GitLabRepositoryNamespace struct {
|
|
|
|
ID int
|
|
|
|
Name string
|
|
|
|
Path string
|
|
|
|
Kind string
|
|
|
|
}
|
|
|
|
|
2022-09-13 22:19:43 +00:00
|
|
|
type GitLabUser struct {
|
2022-09-04 14:36:15 +00:00
|
|
|
ID int
|
|
|
|
Username string
|
|
|
|
Name string
|
|
|
|
State string
|
|
|
|
}
|
|
|
|
|
2022-09-13 22:19:43 +00:00
|
|
|
type GitLabUserKey struct {
|
|
|
|
ID int
|
|
|
|
Key string
|
|
|
|
}
|
|
|
|
|
2022-09-04 14:36:15 +00:00
|
|
|
type GitLabRepository struct {
|
|
|
|
ID int
|
|
|
|
Description string
|
|
|
|
Name string
|
|
|
|
Path string
|
|
|
|
PathWithNamespace string `json:"path_with_namespace"`
|
|
|
|
DefaultBranch string `json:"default_branch"`
|
2022-09-05 10:20:33 +00:00
|
|
|
URL string `json:"url,omitempty"`
|
2022-09-05 01:03:54 +00:00
|
|
|
SshUrlToRepo string `json:"ssh_url_to_repo"`
|
2022-09-04 14:36:15 +00:00
|
|
|
HttpUrlToRepo string `json:"http_url_to_repo"`
|
|
|
|
AvatarURL string `json:"avatar_url"`
|
|
|
|
LastActivityAt time.Time `json:"last_activity_at,omitempty"`
|
|
|
|
Namespace GitLabRepositoryNamespace
|
2022-09-13 22:19:43 +00:00
|
|
|
Visibility string `json:"visibility,omitempty"`
|
|
|
|
Owner *GitLabUser `json:"owner,omitempty"`
|
|
|
|
ForkedFromProject *GitLabRepository `json:"forked_from_project,omitempty"`
|
2022-09-04 14:36:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func GitLab_GetMyRepositories(c *gin.Context) {
|
|
|
|
var u *User
|
|
|
|
if user, ok := c.Get("user"); ok {
|
|
|
|
u = user.(*User)
|
|
|
|
} else {
|
|
|
|
u = c.MustGet("LoggedUser").(*User)
|
|
|
|
}
|
2022-09-04 09:38:10 +00:00
|
|
|
|
2022-09-04 14:36:15 +00:00
|
|
|
repos, err := GitLab_getUsersRepositories(c.Request.Context(), u)
|
2022-09-04 09:38:10 +00:00
|
|
|
if err != nil {
|
2022-09-04 14:36:15 +00:00
|
|
|
log.Println("Unable to perform the GitLab request: ", err.Error())
|
2022-09-04 09:38:10 +00:00
|
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when performing the GitLab request."})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-09-04 14:36:15 +00:00
|
|
|
var repositories []*GitLabRepository
|
|
|
|
for _, r := range repos {
|
|
|
|
if r.Owner.Username == u.Login {
|
|
|
|
repositories = append(repositories, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
c.JSON(http.StatusOK, repos)
|
|
|
|
}
|
|
|
|
|
|
|
|
/*func GitLab_getRepositories(c context.Context) ([]*GitLabRepository, error) {
|
|
|
|
client := oauth2.NewClient(c, gitlaboauth2Config.TokenSource(c, gitlabToken))
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", gitlabBaseURL+"/api/v4/projects?per_page=100", nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-09-04 09:38:10 +00:00
|
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
2022-09-04 14:36:15 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return nil, fmt.Errorf("Bad status code from the API")
|
|
|
|
}
|
|
|
|
|
|
|
|
var repositories []*GitLabRepository
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(&repositories)
|
|
|
|
|
|
|
|
return repositories, err
|
|
|
|
}*/
|
|
|
|
|
|
|
|
func GitLab_getUsersRepositories(c context.Context, u *User) ([]*GitLabRepository, error) {
|
2022-10-17 17:38:17 +00:00
|
|
|
if gitlaboauth2Config == nil || gitlabToken == nil {
|
|
|
|
return nil, fmt.Errorf("the connection with GitLab is not setup yet")
|
|
|
|
}
|
|
|
|
|
2022-09-04 22:54:33 +00:00
|
|
|
client := gitlaboauth2Config.Client(c, gitlabToken())
|
2022-09-04 14:36:15 +00:00
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", gitlabBaseURL+fmt.Sprintf("/api/v4/users/%s/projects?per_page=100", u.Login), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2022-09-04 09:38:10 +00:00
|
|
|
}
|
|
|
|
|
2022-09-04 14:36:15 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return nil, fmt.Errorf("Bad status code from the API")
|
|
|
|
}
|
|
|
|
|
|
|
|
var repositories []*GitLabRepository
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(&repositories)
|
|
|
|
|
|
|
|
return repositories, err
|
2022-09-04 09:38:10 +00:00
|
|
|
}
|
2022-09-13 22:19:43 +00:00
|
|
|
|
|
|
|
func GitLab_getUserId(c context.Context, u *User) (int, error) {
|
|
|
|
client := gitlaboauth2Config.Client(c, gitlabToken())
|
|
|
|
|
|
|
|
val := url.Values{}
|
|
|
|
val.Set("username", u.Login)
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", gitlabBaseURL+fmt.Sprintf("/api/v4/users?%s", val.Encode()), nil)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return 0, fmt.Errorf("Bad status code from the API")
|
|
|
|
}
|
|
|
|
|
|
|
|
var users []*GitLabUser
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(&users)
|
|
|
|
|
|
|
|
if len(users) == 0 {
|
|
|
|
return 0, fmt.Errorf("Login not found in GitLab")
|
|
|
|
}
|
|
|
|
|
|
|
|
return users[0].ID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func GitLab_getUserPGPKeys(c context.Context, u *User) ([]byte, error) {
|
|
|
|
userid, err := GitLab_getUserId(c, u)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
client := gitlaboauth2Config.Client(c, gitlabToken())
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", gitlabBaseURL+fmt.Sprintf("/api/v4/users/%d/gpg_keys", userid), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
rep, _ := ioutil.ReadAll(resp.Body)
|
|
|
|
log.Printf("%d %s", resp.StatusCode, rep)
|
|
|
|
return nil, fmt.Errorf("Bad status code from the API")
|
|
|
|
}
|
|
|
|
|
|
|
|
var keys []*GitLabUserKey
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(&keys)
|
|
|
|
|
|
|
|
var b bytes.Buffer
|
|
|
|
for _, k := range keys {
|
|
|
|
b.Write([]byte(k.Key))
|
|
|
|
b.Write([]byte{'\n'})
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.Bytes(), nil
|
|
|
|
}
|