Newer
Older
/*
* Copyright (c) 2020 InterDigital Communications, Inc
*
* Licensed under the Apache License, Version 2.0 (the \"License\");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an \"AS IS\" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* AdvantEDGE Platform Controller REST API
*
* This API is the main Platform Controller API for scenario configuration & sandbox management <p>**Micro-service**<br>[meep-pfm-ctrl](https://github.com/InterDigitalInc/AdvantEDGE/tree/master/go-apps/meep-platform-ctrl) <p>**Type & Usage**<br>Platform main interface used by controller software to configure scenarios and manage sandboxes in the AdvantEDGE platform <p>**Details**<br>API details available at _your-AdvantEDGE-ip-address/api_
*
* API version: 1.0.0
* Contact: AdvantEDGE@InterDigital.com
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/
package server
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
Kevin Di Lallo
committed
"net/url"
"sync"
dataModel "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-model"
ms "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-metric-store"
Kevin Di Lallo
committed
mq "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-mq"
pcc "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-platform-ctrl-client"
sm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-sessions"
users "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-users"
"github.com/google/go-github/github"
Kevin Di Lallo
committed
"github.com/gorilla/mux"
const OAUTH_PROVIDER_GITHUB = "github"
const OAUTH_PROVIDER_GITLAB = "gitlab"
Kevin Di Lallo
committed
const OAUTH_PROVIDER_LOCAL = "local"
Kevin Di Lallo
committed
const moduleName = "meep-auth-svc"
const moduleNamespace = "default"
const postgisUser = "postgres"
const postgisPwd = "pwd"
const pfmCtrlBasepath = "http://meep-platform-ctrl/platform-ctrl/v1"
Kevin Di Lallo
committed
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Permission Configuration types
type Permission struct {
Mode string `yaml:"mode"`
Roles map[string]string `yaml:"roles"`
}
type Fileserver struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
Sbox bool `yaml:"sbox"`
Mode string `yaml:"mode"`
Roles map[string]string `yaml:"roles"`
}
type Endpoint struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
Method string `yaml:"method"`
Sbox bool `yaml:"sbox"`
Mode string `yaml:"mode"`
Roles map[string]string `yaml:"roles"`
}
type Service struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
Sbox bool `yaml:"sbox"`
Default *Permission `yaml:"default"`
Endpoints []*Endpoint `yaml:"endpoints"`
}
type PermissionsConfig struct {
Default *Permission `yaml:"default"`
Fileservers []*Fileserver `yaml:"fileservers"`
Services []*Service `yaml:"services"`
}
// Auth Service types
type AuthRoute struct {
Name string
Method string
Pattern string
Prefix bool
}
Kevin Di Lallo
committed
type LoginRequest struct {
provider string
timer *time.Timer
}
Kevin Di Lallo
committed
type PermissionsCache struct {
Default *Permission
Fileservers map[string]*Permission
Services map[string]map[string]*Permission
}
Kevin Di Lallo
committed
type AuthSvc struct {
sessionMgr *sm.SessionMgr
userStore *users.Connector
metricStore *ms.MetricStore
mqGlobal *mq.MsgQueue
pfmCtrlClient *pcc.APIClient
maxSessions int
uri string
oauthConfigs map[string]*oauth2.Config
loginRequests map[string]*LoginRequest
Kevin Di Lallo
committed
router *mux.Router
cache PermissionsCache
Kevin Di Lallo
committed
}
var mutex sync.Mutex
var gitlabApiUrl = ""
Kevin Di Lallo
committed
// Declare as variables to enable overwrite in test
var redisDBAddr = "meep-redis-master:6379"
var influxDBAddr string = "http://meep-influxdb.default.svc.cluster.local:8086"
Kevin Di Lallo
committed
// Auth Service
var authSvc *AuthSvc
func Init() (err error) {
// Create new Platform Controller
authSvc = new(AuthSvc)
// Create message queue
authSvc.mqGlobal, err = mq.NewMsgQueue(mq.GetGlobalName(), moduleName, moduleNamespace, redisDBAddr)
if err != nil {
log.Error("Failed to create Message Queue with error: ", err)
return err
}
log.Info("Message Queue created")
// Create Platform Controller REST API client
pfmCtrlClientCfg := pcc.NewConfiguration()
pfmCtrlClientCfg.BasePath = pfmCtrlBasepath
authSvc.pfmCtrlClient = pcc.NewAPIClient(pfmCtrlClientCfg)
if authSvc.pfmCtrlClient == nil {
err := errors.New("Failed to create Platform Ctrl REST API client")
return err
}
log.Info("Platform Ctrl REST API client created")
Kevin Di Lallo
committed
authSvc.sessionMgr, err = sm.NewSessionMgr(moduleName, "", redisDBAddr, redisDBAddr)
if err != nil {
log.Error("Failed connection to Session Manager: ", err.Error())
return err
}
log.Info("Connected to Session Manager")
// Connect to User Store
Kevin Di Lallo
committed
authSvc.userStore, err = users.NewConnector(moduleName, postgisUser, postgisPwd, "", "")
if err != nil {
log.Error("Failed connection to User Store: ", err.Error())
return err
}
Kevin Di Lallo
committed
_ = authSvc.userStore.CreateTables()
Kevin Di Lallo
committed
// Retrieve & cache endpoint authorization permissions
cachePermissions()
// Connect to Metric Store
Kevin Di Lallo
committed
authSvc.metricStore, err = ms.NewMetricStore("session-metrics", "global", influxDBAddr, ms.MetricsDbDisabled)
if err != nil {
log.Error("Failed connection to Metric Store: ", err)
return err
}
// Retrieve maximum session count from environment variable
if maxSessions, err := strconv.ParseInt(os.Getenv("MEEP_MAX_SESSIONS"), 10, 0); err == nil {
Kevin Di Lallo
committed
authSvc.maxSessions = int(maxSessions)
Kevin Di Lallo
committed
log.Info("MEEP_MAX_SESSIONS: ", authSvc.maxSessions)
Kevin Di Lallo
committed
authSvc.uri = strings.TrimSpace(os.Getenv("MEEP_HOST_URL"))
Kevin Di Lallo
committed
authSvc.oauthConfigs = make(map[string]*oauth2.Config)
authSvc.loginRequests = make(map[string]*LoginRequest)
githubEnabledStr := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITHUB_ENABLED"))
githubEnabled, err := strconv.ParseBool(githubEnabledStr)
if err == nil && githubEnabled {
clientId := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITHUB_CLIENT_ID"))
secret := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITHUB_SECRET"))
redirectUri := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITHUB_REDIRECT_URI"))
authUrl := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITHUB_AUTH_URL"))
tokenUrl := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITHUB_TOKEN_URL"))
if clientId != "" && secret != "" && redirectUri != "" && authUrl != "" && tokenUrl != "" {
oauthConfig := &oauth2.Config{
ClientID: clientId,
ClientSecret: secret,
RedirectURL: redirectUri,
Scopes: []string{},
Endpoint: oauth2.Endpoint{
AuthURL: authUrl,
TokenURL: tokenUrl,
},
}
Kevin Di Lallo
committed
authSvc.oauthConfigs[OAUTH_PROVIDER_GITHUB] = oauthConfig
log.Info("GitHub OAuth provider enabled")
Kevin Di Lallo
committed
}
// Initialize GitLab config
gitlabEnabledStr := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITLAB_ENABLED"))
gitlabEnabled, err := strconv.ParseBool(gitlabEnabledStr)
if err == nil && gitlabEnabled {
gitlabApiUrl = strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITLAB_API_URL"))
clientId := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITLAB_CLIENT_ID"))
secret := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITLAB_SECRET"))
redirectUri := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITLAB_REDIRECT_URI"))
authUrl := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITLAB_AUTH_URL"))
tokenUrl := strings.TrimSpace(os.Getenv("MEEP_OAUTH_GITLAB_TOKEN_URL"))
if clientId != "" && secret != "" && redirectUri != "" && authUrl != "" && tokenUrl != "" {
oauthConfig := &oauth2.Config{
ClientID: clientId,
ClientSecret: secret,
RedirectURL: redirectUri,
Scopes: []string{"read_user"},
Endpoint: oauth2.Endpoint{
AuthURL: authUrl,
TokenURL: tokenUrl,
},
}
Kevin Di Lallo
committed
authSvc.oauthConfigs[OAUTH_PROVIDER_GITLAB] = oauthConfig
log.Info("GitLab OAuth provider enabled")
Kevin Di Lallo
committed
}
Kevin Di Lallo
committed
func Run() (err error) {
Kevin Di Lallo
committed
err = authSvc.sessionMgr.StartSessionWatchdog(sessionTimeoutCb)
if err != nil {
log.Error("Failed start Session Watchdog: ", err.Error())
return err
}
return nil
}
Kevin Di Lallo
committed
func getPermissionsConfig() (config *PermissionsConfig, err error) {
// Read & apply API permissions from file
permissionsFile := "/permissions.yaml"
permissions := viper.New()
permissions.SetConfigFile(permissionsFile)
Kevin Di Lallo
committed
err = permissions.ReadInConfig()
if err != nil {
return nil, err
}
// Unmarshal config into Permission Configuration structure
config = new(PermissionsConfig)
err = permissions.Unmarshal(config)
Kevin Di Lallo
committed
return nil, err
}
return config, nil
}
func cachePermissions() {
// Create new Auth Router
authSvc.router = mux.NewRouter().StrictSlash(true)
// Get permissions from configuration file
config, err := getPermissionsConfig()
if err != nil || config == nil {
log.Warn("Failed to retrieve permissions from file with err: ", err.Error())
log.Warn("Granting full API access for all roles by default")
Kevin Di Lallo
committed
// Cache default permission
authSvc.cache.Default = &Permission{Mode: sm.ModeAllow}
Kevin Di Lallo
committed
// log.Info(fmt.Sprintf("%+v\n", config))
// Parse & cache permissions from config file
// IMPORTANT NOTE: Order is important to prevent prefix matches from running first
cacheDefaultPermission(config)
cacheServicePermissions(config)
cacheFileserverPermissions(config)
}
Kevin Di Lallo
committed
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
func cacheDefaultPermission(cfg *PermissionsConfig) {
authSvc.cache.Default = cfg.Default
if authSvc.cache.Default == nil {
log.Warn("Failed to retrieve default permission")
log.Warn("Granting full API access for all roles by default")
permission := new(Permission)
permission.Mode = sm.ModeAllow
authSvc.cache.Default = permission
}
}
func cacheServicePermissions(cfg *PermissionsConfig) {
var routes []*AuthRoute
// Initialize Service permissions cache
authSvc.cache.Services = make(map[string]map[string]*Permission)
for _, svc := range cfg.Services {
// Create new service + add it to service cache
svcMap := make(map[string]*Permission)
authSvc.cache.Services[svc.Name] = svcMap
// Service Endpoints
for _, ep := range svc.Endpoints {
// Cache service endpoint permissions
permission := new(Permission)
permission.Mode = ep.Mode
permission.Roles = make(map[string]string)
for role, access := range ep.Roles {
permission.Roles[role] = access
Kevin Di Lallo
committed
svcMap[ep.Name] = permission
// Add auth service route
route := new(AuthRoute)
route.Name = ep.Name
route.Prefix = false
route.Method = ep.Method
if svc.Sbox {
route.Pattern = "/{sbox}" + svc.Path + ep.Path
} else {
route.Pattern = svc.Path + ep.Path
}
routes = append(routes, route)
}
// fmt.Printf("%+v\n", svcMap)
// Default service permissions
// IMPORTANT NOTE: This prefix route must be added after the service endpoint routes
var permission *Permission
if svc.Default.Mode != "" {
permission := new(Permission)
permission.Roles = make(map[string]string)
permission.Mode = svc.Default.Mode
for role, access := range svc.Default.Roles {
permission.Roles[role] = access
Kevin Di Lallo
committed
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
} else {
// Use cache default permission if service-specific default is not found
permission = authSvc.cache.Default
}
svcMap[svc.Name] = permission
// Add auth service default route
route := new(AuthRoute)
route.Name = svc.Name
route.Prefix = true
if svc.Sbox {
route.Pattern = "/{sbox}" + svc.Path
} else {
route.Pattern = svc.Path
}
routes = append(routes, route)
}
// Add routes to router
addRoutes(routes)
}
func cacheFileserverPermissions(cfg *PermissionsConfig) {
var routes []*AuthRoute
// Initialize Fileserver permissions cache
authSvc.cache.Fileservers = make(map[string]*Permission)
for _, fs := range cfg.Fileservers {
// Cache fileserver permissions
permission := new(Permission)
permission.Mode = fs.Mode
permission.Roles = make(map[string]string)
for role, access := range fs.Roles {
permission.Roles[role] = access
}
authSvc.cache.Fileservers[fs.Name] = permission
// Add auth service route
route := new(AuthRoute)
route.Name = fs.Name
route.Prefix = true
if fs.Sbox {
route.Pattern = "/{sbox}" + fs.Path
} else {
route.Pattern = fs.Path
}
routes = append(routes, route)
}
// Add routes to router
addRoutes(routes)
}
func addRoutes(routes []*AuthRoute) {
for _, route := range routes {
fmt.Printf("%+v\n", route)
if route.Prefix {
authSvc.router.
Name(route.Name).
PathPrefix(route.Pattern)
} else {
authSvc.router.
Name(route.Name).
Methods(route.Method).
Path(route.Pattern)
}
}
}
func sessionTimeoutCb(session *sm.Session) {
log.Info("Session timed out. ID[", session.ID, "] Username[", session.Username, "]")
var metric ms.SessionMetric
metric.Provider = session.Provider
metric.User = session.Username
metric.Sandbox = session.Sandbox
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeTimeout, metric)
Kevin Di Lallo
committed
_, _ = authSvc.pfmCtrlClient.SandboxControlApi.DeleteSandbox(context.TODO(), session.Sandbox)
// Generate a random state string
func generateState(n int) (string, error) {
data := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, data); err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(data), nil
}
func getUniqueState() (state string, err error) {
for i := 0; i < 3; i++ {
// Get random state
randState, err := generateState(20)
if err != nil {
log.Error(err.Error())
return "", err
}
// Make sure state is unique
Kevin Di Lallo
committed
if _, found := authSvc.loginRequests[randState]; !found {
return randState, nil
}
}
return "", errors.New("Failed to generate a random state string")
}
func getLoginRequest(state string) *LoginRequest {
mutex.Lock()
defer mutex.Unlock()
Kevin Di Lallo
committed
request, found := authSvc.loginRequests[state]
if !found {
return nil
}
return request
}
func setLoginRequest(state string, request *LoginRequest) {
mutex.Lock()
defer mutex.Unlock()
Kevin Di Lallo
committed
authSvc.loginRequests[state] = request
}
func delLoginRequest(state string) {
mutex.Lock()
defer mutex.Unlock()
Kevin Di Lallo
committed
request, found := authSvc.loginRequests[state]
if !found {
return
}
if request.timer != nil {
request.timer.Stop()
}
Kevin Di Lallo
committed
delete(authSvc.loginRequests, state)
}
func getErrUrl(err string) string {
Kevin Di Lallo
committed
return authSvc.uri + "?err=" + strings.ReplaceAll(err, " ", "+")
}
Kevin Di Lallo
committed
// ---------- REST API ----------
Kevin Di Lallo
committed
func asAuthenticate(w http.ResponseWriter, r *http.Request) {
Kevin Di Lallo
committed
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
// Get service & sandbox name from request query parameters
query := r.URL.Query()
svcName := query.Get("svc")
// sboxName := query.Get("sbox")
var sboxName string
// Get original request URL & method
originalUrl := r.Header.Get("X-Original-URL")
originalMethod := r.Header.Get("X-Original-Method")
if originalUrl == "" || originalMethod == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Update request URL & method before running through matchers
var err error
r.Method = originalMethod
r.URL, err = url.ParseRequestURI(originalUrl)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Get permissions for matching route or default if not found
var permission *Permission
var match mux.RouteMatch
if authSvc.router.Match(r, &match) {
routeName := match.Route.GetName()
sboxName = match.Vars["sbox"]
log.Error("routeName: ", routeName, " sboxName: ", sboxName)
// Check service-specific routes
if svcName != "" {
if svcPermissions, found := authSvc.cache.Services[svcName]; found {
permission = svcPermissions[routeName]
}
}
// Check file servers if not already found
if permission == nil {
if fsPermission, found := authSvc.cache.Fileservers[routeName]; found {
permission = fsPermission
}
}
} else {
permission = authSvc.cache.Default
}
// Verify permission
if permission == nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
Kevin Di Lallo
committed
// Handle according to permission mode
switch permission.Mode {
Kevin Di Lallo
committed
case sm.ModeAllow:
// break
Kevin Di Lallo
committed
case sm.ModeBlock:
http.Error(w, "Unauthorized", http.StatusUnauthorized)
Kevin Di Lallo
committed
case sm.ModeVerify:
// Retrieve user session, if any
Kevin Di Lallo
committed
session, err := authSvc.sessionMgr.GetSessionStore().Get(r)
Kevin Di Lallo
committed
if err != nil || session == nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
Kevin Di Lallo
committed
// Verify role permissions
role := session.Role
if role == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
Kevin Di Lallo
committed
access := permission.Roles[role]
Kevin Di Lallo
committed
if access != sm.AccessGranted {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
Kevin Di Lallo
committed
// For non-admin users, verify session sandbox matches service sandbox, if any
if session.Role != sm.RoleAdmin && sboxName != "" && sboxName != session.Sandbox {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
Kevin Di Lallo
committed
Kevin Di Lallo
committed
default:
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
Kevin Di Lallo
committed
// Allow request
Kevin Di Lallo
committed
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
Kevin Di Lallo
committed
// Invoke handler
// handler.ServeHTTP(w, r)
// handler(w, r)
// authSvc.router.ServeHTTP(w, r)
Kevin Di Lallo
committed
func asAuthorize(w http.ResponseWriter, r *http.Request) {
var metric ms.SessionMetric
// Retrieve query parameters
query := r.URL.Query()
code := query.Get("code")
state := query.Get("state")
// Validate request state
request := getLoginRequest(state)
if request == nil {
err := errors.New("Invalid OAuth state")
log.Error(err.Error())
metric.Description = err.Error()
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Redirect(w, r, getErrUrl(err.Error()), http.StatusFound)
Kevin Di Lallo
committed
// Get provider-specific OAuth config
provider := request.provider
Kevin Di Lallo
committed
config, found := authSvc.oauthConfigs[provider]
Kevin Di Lallo
committed
if !found {
err := errors.New("Provider config not found for: " + provider)
log.Error(err.Error())
metric.Description = err.Error()
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
Kevin Di Lallo
committed
http.Redirect(w, r, getErrUrl(err.Error()), http.StatusFound)
return
}
metric.Provider = provider
Kevin Di Lallo
committed
// Delete login request & timer
delLoginRequest(state)
// Retrieve access token
token, err := config.Exchange(context.Background(), code)
if err != nil {
log.Error(err.Error())
metric.Description = err.Error()
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Redirect(w, r, getErrUrl(err.Error()), http.StatusFound)
oauthClient := config.Client(context.Background(), token)
if oauthClient == nil {
err = errors.New("Failed to create new oauth client")
log.Error(err.Error())
metric.Description = err.Error()
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Redirect(w, r, getErrUrl(err.Error()), http.StatusFound)
return
}
Kevin Di Lallo
committed
switch provider {
case OAUTH_PROVIDER_GITHUB:
client := github.NewClient(oauthClient)
if client == nil {
err = errors.New("Failed to create new GitHub client")
log.Error(err.Error())
metric.Description = err.Error()
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Redirect(w, r, getErrUrl(err.Error()), http.StatusFound)
return
}
user, _, err := client.Users.Get(context.Background(), "")
if err != nil {
log.Error(err.Error())
metric.Description = err.Error()
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Redirect(w, r, getErrUrl("Failed to retrieve GitHub user ID"), http.StatusFound)
client := gitlab.NewOAuthClient(oauthClient, token.AccessToken)
if client == nil {
err = errors.New("Failed to create new GitLab client")
log.Error(err.Error())
metric.Description = err.Error()
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Redirect(w, r, getErrUrl(err.Error()), http.StatusFound)
// Override default gitlab base URL
if gitlabApiUrl != "" {
err = client.SetBaseURL(gitlabApiUrl)
if err != nil {
log.Error(err.Error())
metric.Description = err.Error()
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Redirect(w, r, getErrUrl("Failed to set GitLab API base url"), http.StatusFound)
return
}
}
user, _, err := client.Users.CurrentUser()
if err != nil {
log.Error(err.Error())
metric.Description = err.Error()
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Redirect(w, r, getErrUrl("Failed to retrieve GitLab user ID"), http.StatusFound)
return
}
userId = user.Username
default:
}
Kevin Di Lallo
committed
sandboxName, err, errCode := startSession(provider, userId, w, r)
if err != nil {
log.Error(err.Error())
metric.Description = err.Error()
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Redirect(w, r, getErrUrl(err.Error()), errCode)
metric.Sandbox = sandboxName
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeLogin, metric)
Kevin Di Lallo
committed
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
http.Redirect(w, r, authSvc.uri+"?sbox="+sandboxName+"&user="+userId, http.StatusFound)
}
func asLogin(w http.ResponseWriter, r *http.Request) {
log.Info("----- OAUTH LOGIN -----")
var metric ms.SessionMetric
// Retrieve query parameters
query := r.URL.Query()
provider := query.Get("provider")
metric.Provider = provider
// Get provider-specific OAuth config
config, found := authSvc.oauthConfigs[provider]
if !found {
err := errors.New("Provider config not found for: " + provider)
log.Error(err.Error())
metric.Description = err.Error()
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Redirect(w, r, getErrUrl(err.Error()), http.StatusFound)
return
}
// Generate unique random state string
state, err := getUniqueState()
if err != nil {
log.Error(err.Error())
metric.Description = err.Error()
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Redirect(w, r, getErrUrl(err.Error()), http.StatusFound)
return
}
// Track oauth request & handle
request := &LoginRequest{
provider: provider,
timer: time.NewTimer(10 * time.Minute),
}
setLoginRequest(state, request)
// Start timer to remove request from map
go func() {
<-request.timer.C
delLoginRequest(state)
}()
// Generate provider-specific oauth redirect
uri := config.AuthCodeURL(state, oauth2.AccessTypeOnline)
http.Redirect(w, r, uri, http.StatusFound)
Kevin Di Lallo
committed
func asLoginUser(w http.ResponseWriter, r *http.Request) {
// Get form data
username := r.FormValue("username")
password := r.FormValue("password")
metric.Provider = OAUTH_PROVIDER_LOCAL
metric.User = username
// Validate user credentials
Kevin Di Lallo
committed
authenticated, err := authSvc.userStore.AuthenticateUser(OAUTH_PROVIDER_LOCAL, username, password)
if err != nil || !authenticated {
if err != nil {
metric.Description = err.Error()
} else {
metric.Description = "Unauthorized"
}
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
Kevin Di Lallo
committed
sandboxName, err, errCode := startSession(OAUTH_PROVIDER_LOCAL, username, w, r)
if err != nil {
log.Error(err.Error())
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeError, metric)
http.Error(w, err.Error(), errCode)
return
}
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeLogin, metric)
// Prepare response
var sandbox dataModel.Sandbox
sandbox.Name = sandboxName
// Format response
jsonResponse, err := json.Marshal(sandbox)
if err != nil {
log.Error(err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Send response
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, string(jsonResponse))
}
// Retrieve existing user session or create a new one
Kevin Di Lallo
committed
func startSession(provider string, username string, w http.ResponseWriter, r *http.Request) (sandboxName string, err error, code int) {
// Get existing session by user name, if any
Kevin Di Lallo
committed
sessionStore := authSvc.sessionMgr.GetSessionStore()
Kevin Di Lallo
committed
session, err := sessionStore.GetByName(provider, username)
// Check if max session count is reached before creating a new one
count := sessionStore.GetCount()
Kevin Di Lallo
committed
if count >= authSvc.maxSessions {
err = errors.New("Maximum session count exceeded")
return "", err, http.StatusServiceUnavailable
// Get requested sandbox name & role from user profile, if any
role := users.RoleUser
Kevin Di Lallo
committed
user, err := authSvc.userStore.GetUser(provider, username)
Kevin Di Lallo
committed
if err == nil {
sandboxName = user.Sboxname
Kevin Di Lallo
committed
}
Kevin Di Lallo
committed
// Create sandbox
var sandboxConfig pcc.SandboxConfig
Kevin Di Lallo
committed
sandbox, _, err := authSvc.pfmCtrlClient.SandboxControlApi.CreateSandbox(context.TODO(), sandboxConfig)
if err != nil {
return "", err, http.StatusInternalServerError
}
sandboxName = sandbox.Name
} else {
_, err := authSvc.pfmCtrlClient.SandboxControlApi.CreateSandboxWithName(context.TODO(), sandboxName, sandboxConfig)
if err != nil {
return "", err, http.StatusInternalServerError
Kevin Di Lallo
committed
}
session.ID = ""
session.Username = username
Kevin Di Lallo
committed
session.Provider = provider
sandboxName = session.Sandbox
}
// Set session
err, code = sessionStore.Set(session, w, r)
if err != nil {
log.Error("Failed to set session with err: ", err.Error())
// Remove newly created sandbox on failure
if session.ID == "" {
Kevin Di Lallo
committed
_, _ = authSvc.pfmCtrlClient.SandboxControlApi.DeleteSandbox(context.TODO(), sandboxName)
Kevin Di Lallo
committed
func asLogout(w http.ResponseWriter, r *http.Request) {
var metric ms.SessionMetric
Kevin Di Lallo
committed
sessionStore := authSvc.sessionMgr.GetSessionStore()
session, err := sessionStore.Get(r)
metric.Provider = session.Provider
metric.User = session.Username
metric.Sandbox = session.Sandbox
Kevin Di Lallo
committed
_, _ = authSvc.pfmCtrlClient.SandboxControlApi.DeleteSandbox(context.TODO(), session.Sandbox)
log.Error("Failed to delete session with err: ", err.Error())
Kevin Di Lallo
committed
_ = authSvc.metricStore.SetSessionMetric(ms.SesMetTypeLogout, metric)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
}
Kevin Di Lallo
committed
Kevin Di Lallo
committed
func asTriggerWatchdog(w http.ResponseWriter, r *http.Request) {
Kevin Di Lallo
committed
// Refresh session
Kevin Di Lallo
committed
sessionStore := authSvc.sessionMgr.GetSessionStore()
Kevin Di Lallo
committed
if err != nil {
log.Error("Failed to refresh session with err: ", err.Error())
Kevin Di Lallo
committed
return
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
}