Add support for openid with github and facebook
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
play-with-docker
|
play-with-docker
|
||||||
node_modules
|
node_modules
|
||||||
docker-compose.single.yml
|
docker-compose.single.yml
|
||||||
|
lala
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/securecookie"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
oauth2FB "golang.org/x/oauth2/facebook"
|
||||||
|
oauth2Github "golang.org/x/oauth2/github"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -21,12 +27,17 @@ const (
|
|||||||
var NameFilter = regexp.MustCompile(PWDHostPortGroupRegex)
|
var NameFilter = regexp.MustCompile(PWDHostPortGroupRegex)
|
||||||
var AliasFilter = regexp.MustCompile(AliasPortGroupRegex)
|
var AliasFilter = regexp.MustCompile(AliasPortGroupRegex)
|
||||||
|
|
||||||
var PortNumber, Key, Cert, SessionsFile, PWDContainerName, L2ContainerName, L2Subdomain, PWDCName, HashKey, SSHKeyPath, L2RouterIP, DindVolumeSize string
|
var PortNumber, Key, Cert, SessionsFile, PWDContainerName, L2ContainerName, L2Subdomain, PWDCName, HashKey, SSHKeyPath, L2RouterIP, DindVolumeSize, CookieHashKey, CookieBlockKey string
|
||||||
var UseLetsEncrypt, ExternalDindVolume, NoWindows bool
|
var UseLetsEncrypt, ExternalDindVolume, NoWindows bool
|
||||||
var LetsEncryptCertsDir string
|
var LetsEncryptCertsDir string
|
||||||
var LetsEncryptDomains stringslice
|
var LetsEncryptDomains stringslice
|
||||||
var MaxLoadAvg float64
|
var MaxLoadAvg float64
|
||||||
var ForceTLS bool
|
var ForceTLS bool
|
||||||
|
var Providers map[string]*oauth2.Config
|
||||||
|
var SecureCookie *securecookie.SecureCookie
|
||||||
|
|
||||||
|
var GithubClientID, GithubClientSecret string
|
||||||
|
var FacebookClientID, FacebookClientSecret string
|
||||||
|
|
||||||
type stringslice []string
|
type stringslice []string
|
||||||
|
|
||||||
@@ -58,8 +69,46 @@ func ParseFlags() {
|
|||||||
flag.BoolVar(&ExternalDindVolume, "external-dind-volume", false, "Use external dind volume")
|
flag.BoolVar(&ExternalDindVolume, "external-dind-volume", false, "Use external dind volume")
|
||||||
flag.Float64Var(&MaxLoadAvg, "maxload", 100, "Maximum allowed load average before failing ping requests")
|
flag.Float64Var(&MaxLoadAvg, "maxload", 100, "Maximum allowed load average before failing ping requests")
|
||||||
flag.StringVar(&SSHKeyPath, "ssh_key_path", "", "SSH Private Key to use")
|
flag.StringVar(&SSHKeyPath, "ssh_key_path", "", "SSH Private Key to use")
|
||||||
|
flag.StringVar(&CookieHashKey, "cookie-hash-key", "", "Hash key to use to validate cookies")
|
||||||
|
flag.StringVar(&CookieBlockKey, "cookie-block-key", "", "Block key to use to encrypt cookies")
|
||||||
|
|
||||||
|
flag.StringVar(&GithubClientID, "github-client-id", "", "Github OAuth Client ID")
|
||||||
|
flag.StringVar(&GithubClientSecret, "github-client-secret", "", "Github OAuth Client Secret")
|
||||||
|
|
||||||
|
flag.StringVar(&FacebookClientID, "facebook-client-id", "", "Facebook OAuth Client ID")
|
||||||
|
flag.StringVar(&FacebookClientSecret, "facebook-client-secret", "", "Facebook OAuth Client Secret")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
SecureCookie = securecookie.New([]byte(CookieHashKey), []byte(CookieBlockKey))
|
||||||
|
|
||||||
|
registerOAuthProviders()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func registerOAuthProviders() {
|
||||||
|
Providers = map[string]*oauth2.Config{}
|
||||||
|
if GithubClientID != "" && GithubClientSecret != "" {
|
||||||
|
conf := &oauth2.Config{
|
||||||
|
ClientID: GithubClientID,
|
||||||
|
ClientSecret: GithubClientSecret,
|
||||||
|
Scopes: []string{"user:email"},
|
||||||
|
Endpoint: oauth2Github.Endpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
Providers["github"] = conf
|
||||||
|
}
|
||||||
|
if FacebookClientID != "" && FacebookClientSecret != "" {
|
||||||
|
conf := &oauth2.Config{
|
||||||
|
ClientID: FacebookClientID,
|
||||||
|
ClientSecret: FacebookClientSecret,
|
||||||
|
Scopes: []string{"email", "public_profile"},
|
||||||
|
Endpoint: oauth2FB.Endpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
Providers["facebook"] = conf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetDindImageName() string {
|
func GetDindImageName() string {
|
||||||
dindImage := os.Getenv("DIND_IMAGE")
|
dindImage := os.Getenv("DIND_IMAGE")
|
||||||
defaultDindImageName := "franela/dind"
|
defaultDindImageName := "franela/dind"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
@@ -16,7 +15,6 @@ import (
|
|||||||
"github.com/play-with-docker/play-with-docker/config"
|
"github.com/play-with-docker/play-with-docker/config"
|
||||||
"github.com/play-with-docker/play-with-docker/event"
|
"github.com/play-with-docker/play-with-docker/event"
|
||||||
"github.com/play-with-docker/play-with-docker/pwd"
|
"github.com/play-with-docker/play-with-docker/pwd"
|
||||||
"github.com/play-with-docker/play-with-docker/templates"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/urfave/negroni"
|
"github.com/urfave/negroni"
|
||||||
)
|
)
|
||||||
@@ -33,9 +31,6 @@ func Bootstrap(c pwd.PWDApi, ev event.EventApi) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Register(extend HandlerExtender) {
|
func Register(extend HandlerExtender) {
|
||||||
|
|
||||||
bypassCaptcha := len(os.Getenv("GOOGLE_RECAPTCHA_DISABLED")) > 0
|
|
||||||
|
|
||||||
server, err := socketio.NewServer(nil)
|
server, err := socketio.NewServer(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -81,17 +76,13 @@ func Register(extend HandlerExtender) {
|
|||||||
|
|
||||||
// Generic routes
|
// Generic routes
|
||||||
r.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||||
if bypassCaptcha {
|
http.ServeFile(rw, r, "./www/landing.html")
|
||||||
http.ServeFile(rw, r, "./www/bypass.html")
|
|
||||||
} else {
|
|
||||||
welcome, tmplErr := templates.GetWelcomeTemplate()
|
|
||||||
if tmplErr != nil {
|
|
||||||
log.Fatal(tmplErr)
|
|
||||||
}
|
|
||||||
rw.Write(welcome)
|
|
||||||
}
|
|
||||||
}).Methods("GET")
|
}).Methods("GET")
|
||||||
|
|
||||||
|
r.HandleFunc("/oauth/providers", ListProviders).Methods("GET")
|
||||||
|
r.HandleFunc("/oauth/providers/{provider}/login", Login).Methods("GET")
|
||||||
|
r.HandleFunc("/oauth/providers/{provider}/callback", LoginCallback).Methods("GET")
|
||||||
|
|
||||||
corsRouter.HandleFunc("/", NewSession).Methods("POST")
|
corsRouter.HandleFunc("/", NewSession).Methods("POST")
|
||||||
|
|
||||||
if extend != nil {
|
if extend != nil {
|
||||||
|
|||||||
40
handlers/cookie_id.go
Normal file
40
handlers/cookie_id.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/play-with-docker/play-with-docker/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CookieID struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
UserName string `json:"user_name"`
|
||||||
|
UserAvatar string `json:"user_avatar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CookieID) SetCookie(rw http.ResponseWriter) error {
|
||||||
|
if encoded, err := config.SecureCookie.Encode("id", c); err == nil {
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: "id",
|
||||||
|
Value: encoded,
|
||||||
|
Path: "/",
|
||||||
|
Secure: config.UseLetsEncrypt,
|
||||||
|
}
|
||||||
|
http.SetCookie(rw, cookie)
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func ReadCookie(r *http.Request) (*CookieID, error) {
|
||||||
|
if cookie, err := r.Cookie("id"); err == nil {
|
||||||
|
value := &CookieID{}
|
||||||
|
if err = config.SecureCookie.Decode("id", cookie.Value, &value); err == nil {
|
||||||
|
return value, nil
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
146
handlers/login.go
Normal file
146
handlers/login.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
|
"github.com/google/go-github/github"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
fb "github.com/huandu/facebook"
|
||||||
|
"github.com/play-with-docker/play-with-docker/config"
|
||||||
|
"github.com/play-with-docker/play-with-docker/pwd/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListProviders(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
providers := []string{}
|
||||||
|
for name, _ := range config.Providers {
|
||||||
|
providers = append(providers, name)
|
||||||
|
}
|
||||||
|
json.NewEncoder(rw).Encode(providers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Login(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
providerName := vars["provider"]
|
||||||
|
|
||||||
|
provider, found := config.Providers[providerName]
|
||||||
|
if !found {
|
||||||
|
log.Printf("Could not find provider %s\n", providerName)
|
||||||
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loginRequest, err := core.UserNewLoginRequest(providerName)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not start a new user login request for provider %s. Got: %v\n", providerName, err)
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme := "http"
|
||||||
|
if req.URL.Scheme != "" {
|
||||||
|
scheme = req.URL.Scheme
|
||||||
|
}
|
||||||
|
host := "localhost"
|
||||||
|
if req.URL.Host != "" {
|
||||||
|
host = req.URL.Host
|
||||||
|
}
|
||||||
|
provider.RedirectURL = fmt.Sprintf("%s://%s/oauth/providers/%s/callback", scheme, host, providerName)
|
||||||
|
url := provider.AuthCodeURL(loginRequest.Id)
|
||||||
|
|
||||||
|
http.Redirect(rw, req, url, http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoginCallback(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
providerName := vars["provider"]
|
||||||
|
|
||||||
|
provider, found := config.Providers[providerName]
|
||||||
|
if !found {
|
||||||
|
log.Printf("Could not find provider %s\n", providerName)
|
||||||
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query := req.URL.Query()
|
||||||
|
|
||||||
|
code := query.Get("code")
|
||||||
|
loginRequestId := query.Get("state")
|
||||||
|
|
||||||
|
loginRequest, err := core.UserGetLoginRequest(loginRequestId)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not get login request %s for provider %s. Got: %v\n", loginRequestId, providerName, err)
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := req.Context()
|
||||||
|
tok, err := provider.Exchange(ctx, code)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not exchage code for access token for provider %s. Got: %v\n", providerName, err)
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &types.User{Provider: providerName}
|
||||||
|
if providerName == "github" {
|
||||||
|
ts := oauth2.StaticTokenSource(
|
||||||
|
&oauth2.Token{AccessToken: tok.AccessToken},
|
||||||
|
)
|
||||||
|
tc := oauth2.NewClient(ctx, ts)
|
||||||
|
client := github.NewClient(tc)
|
||||||
|
u, _, err := client.Users.Get(ctx, "")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not get user from github. Got: %v\n", err)
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.ProviderUserId = strconv.Itoa(u.GetID())
|
||||||
|
user.Name = u.GetName()
|
||||||
|
user.Avatar = u.GetAvatarURL()
|
||||||
|
user.Email = u.GetEmail()
|
||||||
|
} else if providerName == "facebook" {
|
||||||
|
ts := oauth2.StaticTokenSource(
|
||||||
|
&oauth2.Token{AccessToken: tok.AccessToken},
|
||||||
|
)
|
||||||
|
tc := oauth2.NewClient(ctx, ts)
|
||||||
|
session := &fb.Session{
|
||||||
|
Version: "v2.10",
|
||||||
|
HttpClient: tc,
|
||||||
|
}
|
||||||
|
p := fb.Params{}
|
||||||
|
p["fields"] = "email,name,picture"
|
||||||
|
res, err := session.Get("/me", p)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not get user from facebook. Got: %v\n", err)
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.ProviderUserId = res.Get("id").(string)
|
||||||
|
user.Name = res.Get("name").(string)
|
||||||
|
user.Avatar = res.Get("picture.data.url").(string)
|
||||||
|
user.Email = res.Get("email").(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err = core.UserLogin(loginRequest, user)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not login user. Got: %v\n", err)
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieData := CookieID{Id: user.Id, UserName: user.Name, UserAvatar: user.Avatar}
|
||||||
|
|
||||||
|
if err := cookieData.SetCookie(rw); err != nil {
|
||||||
|
log.Printf("Could not encode cookie. Got: %v\n", err)
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(rw, req, "/", http.StatusFound)
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/play-with-docker/play-with-docker/config"
|
"github.com/play-with-docker/play-with-docker/config"
|
||||||
"github.com/play-with-docker/play-with-docker/provisioner"
|
"github.com/play-with-docker/play-with-docker/provisioner"
|
||||||
"github.com/play-with-docker/play-with-docker/recaptcha"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type NewSessionResponse struct {
|
type NewSessionResponse struct {
|
||||||
@@ -20,10 +19,16 @@ type NewSessionResponse struct {
|
|||||||
|
|
||||||
func NewSession(rw http.ResponseWriter, req *http.Request) {
|
func NewSession(rw http.ResponseWriter, req *http.Request) {
|
||||||
req.ParseForm()
|
req.ParseForm()
|
||||||
if !recaptcha.IsHuman(req, rw) {
|
|
||||||
// User it not a human
|
userId := ""
|
||||||
rw.WriteHeader(http.StatusForbidden)
|
if len(config.Providers) > 0 {
|
||||||
return
|
cookie, err := ReadCookie(req)
|
||||||
|
if err != nil {
|
||||||
|
// User it not a human
|
||||||
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userId = cookie.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
reqDur := req.Form.Get("session-duration")
|
reqDur := req.Form.Get("session-duration")
|
||||||
@@ -45,7 +50,7 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
duration := config.GetDuration(reqDur)
|
duration := config.GetDuration(reqDur)
|
||||||
s, err := core.SessionNew(duration, stack, stackName, imageName)
|
s, err := core.SessionNew(userId, duration, stack, stackName, imageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if provisioner.OutOfCapacity(err) {
|
if provisioner.OutOfCapacity(err) {
|
||||||
http.Redirect(rw, req, "/ooc", http.StatusFound)
|
http.Redirect(rw, req, "/ooc", http.StatusFound)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func TestClientNew(t *testing.T) {
|
|||||||
p := NewPWD(_f, _e, _s, sp, ipf)
|
p := NewPWD(_f, _e, _s, sp, ipf)
|
||||||
p.generator = _g
|
p.generator = _g
|
||||||
|
|
||||||
session, err := p.SessionNew(time.Hour, "", "", "")
|
session, err := p.SessionNew("", time.Hour, "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
client := p.ClientNew("foobar", session)
|
client := p.ClientNew("foobar", session)
|
||||||
@@ -82,7 +82,7 @@ func TestClientCount(t *testing.T) {
|
|||||||
p := NewPWD(_f, _e, _s, sp, ipf)
|
p := NewPWD(_f, _e, _s, sp, ipf)
|
||||||
p.generator = _g
|
p.generator = _g
|
||||||
|
|
||||||
session, err := p.SessionNew(time.Hour, "", "", "")
|
session, err := p.SessionNew("", time.Hour, "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
p.ClientNew("foobar", session)
|
p.ClientNew("foobar", session)
|
||||||
@@ -123,7 +123,7 @@ func TestClientResizeViewPort(t *testing.T) {
|
|||||||
p := NewPWD(_f, _e, _s, sp, ipf)
|
p := NewPWD(_f, _e, _s, sp, ipf)
|
||||||
p.generator = _g
|
p.generator = _g
|
||||||
|
|
||||||
session, err := p.SessionNew(time.Hour, "", "", "")
|
session, err := p.SessionNew("", time.Hour, "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
client := p.ClientNew("foobar", session)
|
client := p.ClientNew("foobar", session)
|
||||||
_s.On("ClientFindBySessionId", "aaaabbbbcccc").Return([]*types.Client{client}, nil)
|
_s.On("ClientFindBySessionId", "aaaabbbbcccc").Return([]*types.Client{client}, nil)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func TestInstanceNew(t *testing.T) {
|
|||||||
p := NewPWD(_f, _e, _s, sp, ipf)
|
p := NewPWD(_f, _e, _s, sp, ipf)
|
||||||
p.generator = _g
|
p.generator = _g
|
||||||
|
|
||||||
session, err := p.SessionNew(time.Hour, "", "", "")
|
session, err := p.SessionNew("", time.Hour, "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
expectedInstance := types.Instance{
|
expectedInstance := types.Instance{
|
||||||
@@ -138,7 +138,7 @@ func TestInstanceNew_WithNotAllowedImage(t *testing.T) {
|
|||||||
p := NewPWD(_f, _e, _s, sp, ipf)
|
p := NewPWD(_f, _e, _s, sp, ipf)
|
||||||
p.generator = _g
|
p.generator = _g
|
||||||
|
|
||||||
session, err := p.SessionNew(time.Hour, "", "", "")
|
session, err := p.SessionNew("", time.Hour, "", "", "")
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
@@ -207,7 +207,7 @@ func TestInstanceNew_WithCustomHostname(t *testing.T) {
|
|||||||
p := NewPWD(_f, _e, _s, sp, ipf)
|
p := NewPWD(_f, _e, _s, sp, ipf)
|
||||||
p.generator = _g
|
p.generator = _g
|
||||||
|
|
||||||
session, err := p.SessionNew(time.Hour, "", "", "")
|
session, err := p.SessionNew("", time.Hour, "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
expectedInstance := types.Instance{
|
expectedInstance := types.Instance{
|
||||||
|
|||||||
17
pwd/mock.go
17
pwd/mock.go
@@ -13,7 +13,7 @@ type Mock struct {
|
|||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mock) SessionNew(duration time.Duration, stack string, stackName, imageName string) (*types.Session, error) {
|
func (m *Mock) SessionNew(userId string, duration time.Duration, stack string, stackName, imageName string) (*types.Session, error) {
|
||||||
args := m.Called(duration, stack, stackName, imageName)
|
args := m.Called(duration, stack, stackName, imageName)
|
||||||
return args.Get(0).(*types.Session), args.Error(1)
|
return args.Get(0).(*types.Session), args.Error(1)
|
||||||
}
|
}
|
||||||
@@ -104,3 +104,18 @@ func (m *Mock) ClientCount() int {
|
|||||||
args := m.Called()
|
args := m.Called()
|
||||||
return args.Int(0)
|
return args.Int(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Mock) UserNewLoginRequest(providerName string) (*types.LoginRequest, error) {
|
||||||
|
args := m.Called(providerName)
|
||||||
|
return args.Get(0).(*types.LoginRequest), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mock) UserGetLoginRequest(id string) (*types.LoginRequest, error) {
|
||||||
|
args := m.Called(id)
|
||||||
|
return args.Get(0).(*types.LoginRequest), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mock) UserLogin(loginRequest *types.LoginRequest, user *types.User) (*types.User, error) {
|
||||||
|
args := m.Called(loginRequest, user)
|
||||||
|
return args.Get(0).(*types.User), args.Error(1)
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func SessionNotEmpty(e error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PWDApi interface {
|
type PWDApi interface {
|
||||||
SessionNew(duration time.Duration, stack string, stackName, imageName string) (*types.Session, error)
|
SessionNew(userId string, duration time.Duration, stack string, stackName, imageName string) (*types.Session, error)
|
||||||
SessionClose(session *types.Session) error
|
SessionClose(session *types.Session) error
|
||||||
SessionGetSmallestViewPort(sessionId string) types.ViewPort
|
SessionGetSmallestViewPort(sessionId string) types.ViewPort
|
||||||
SessionDeployStack(session *types.Session) error
|
SessionDeployStack(session *types.Session) error
|
||||||
@@ -93,6 +93,10 @@ type PWDApi interface {
|
|||||||
ClientResizeViewPort(client *types.Client, cols, rows uint)
|
ClientResizeViewPort(client *types.Client, cols, rows uint)
|
||||||
ClientClose(client *types.Client)
|
ClientClose(client *types.Client)
|
||||||
ClientCount() int
|
ClientCount() int
|
||||||
|
|
||||||
|
UserNewLoginRequest(providerName string) (*types.LoginRequest, error)
|
||||||
|
UserGetLoginRequest(id string) (*types.LoginRequest, error)
|
||||||
|
UserLogin(loginRequest *types.LoginRequest, user *types.User) (*types.User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPWD(f docker.FactoryApi, e event.EventApi, s storage.StorageApi, sp provisioner.SessionProvisionerApi, ipf provisioner.InstanceProvisionerFactoryApi) *pwd {
|
func NewPWD(f docker.FactoryApi, e event.EventApi, s storage.StorageApi, sp provisioner.SessionProvisionerApi, ipf provisioner.InstanceProvisionerFactoryApi) *pwd {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ type SessionSetupInstanceConf struct {
|
|||||||
Tls bool `json:"tls"`
|
Tls bool `json:"tls"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pwd) SessionNew(duration time.Duration, stack, stackName, imageName string) (*types.Session, error) {
|
func (p *pwd) SessionNew(userId string, duration time.Duration, stack, stackName, imageName string) (*types.Session, error) {
|
||||||
defer observeAction("SessionNew", time.Now())
|
defer observeAction("SessionNew", time.Now())
|
||||||
|
|
||||||
s := &types.Session{}
|
s := &types.Session{}
|
||||||
@@ -53,6 +53,7 @@ func (p *pwd) SessionNew(duration time.Duration, stack, stackName, imageName str
|
|||||||
s.ExpiresAt = s.CreatedAt.Add(duration)
|
s.ExpiresAt = s.CreatedAt.Add(duration)
|
||||||
s.Ready = true
|
s.Ready = true
|
||||||
s.Stack = stack
|
s.Stack = stack
|
||||||
|
s.UserId = userId
|
||||||
|
|
||||||
if s.Stack != "" {
|
if s.Stack != "" {
|
||||||
s.Ready = false
|
s.Ready = false
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func TestSessionNew(t *testing.T) {
|
|||||||
|
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
|
|
||||||
s, e := p.SessionNew(time.Hour, "", "", "")
|
s, e := p.SessionNew("", time.Hour, "", "", "")
|
||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
assert.NotNil(t, s)
|
assert.NotNil(t, s)
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ func TestSessionNew(t *testing.T) {
|
|||||||
assert.WithinDuration(t, s.ExpiresAt, before.Add(time.Hour), time.Second)
|
assert.WithinDuration(t, s.ExpiresAt, before.Add(time.Hour), time.Second)
|
||||||
assert.True(t, s.Ready)
|
assert.True(t, s.Ready)
|
||||||
|
|
||||||
s, _ = p.SessionNew(time.Hour, "stackPath", "stackName", "imageName")
|
s, _ = p.SessionNew("", time.Hour, "stackPath", "stackName", "imageName")
|
||||||
|
|
||||||
assert.Equal(t, "stackPath", s.Stack)
|
assert.Equal(t, "stackPath", s.Stack)
|
||||||
assert.Equal(t, "stackName", s.StackName)
|
assert.Equal(t, "stackName", s.StackName)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type Session struct {
|
|||||||
StackName string `json:"stack_name" bson:"stack_name"`
|
StackName string `json:"stack_name" bson:"stack_name"`
|
||||||
ImageName string `json:"image_name" bson:"image_name"`
|
ImageName string `json:"image_name" bson:"image_name"`
|
||||||
Host string `json:"host" bson:"host"`
|
Host string `json:"host" bson:"host"`
|
||||||
|
UserId string `json:"user_id" bson:"user_id"`
|
||||||
rw sync.Mutex `json:"-"`
|
rw sync.Mutex `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
pwd/types/user.go
Normal file
15
pwd/types/user.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Id string `json:"id" bson:"id"`
|
||||||
|
Name string `json:"name" bson:"name"`
|
||||||
|
ProviderUserId string `json:"provider_user_id" bson:"provider_user_id"`
|
||||||
|
Avatar string `json:"avatar" bson:"avatar"`
|
||||||
|
Provider string `json:"provider" bson:"provider"`
|
||||||
|
Email string `json:"email" bson:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginRequest struct {
|
||||||
|
Id string `json:"id" bson:"id"`
|
||||||
|
Provider string `json:"provider" bson:"provider"`
|
||||||
|
}
|
||||||
43
pwd/user.go
Normal file
43
pwd/user.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package pwd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/play-with-docker/play-with-docker/pwd/types"
|
||||||
|
"github.com/play-with-docker/play-with-docker/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *pwd) UserNewLoginRequest(providerName string) (*types.LoginRequest, error) {
|
||||||
|
req := &types.LoginRequest{Id: p.generator.NewId(), Provider: providerName}
|
||||||
|
if err := p.storage.LoginRequestPut(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pwd) UserGetLoginRequest(id string) (*types.LoginRequest, error) {
|
||||||
|
if req, err := p.storage.LoginRequestGet(id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pwd) UserLogin(loginRequest *types.LoginRequest, user *types.User) (*types.User, error) {
|
||||||
|
if err := p.storage.LoginRequestDelete(loginRequest.Id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u, err := p.storage.UserFindByProvider(user.Provider, user.ProviderUserId)
|
||||||
|
if err != nil {
|
||||||
|
if storage.NotFound(err) {
|
||||||
|
user.Id = p.generator.NewId()
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user.Id = u.Id
|
||||||
|
}
|
||||||
|
if err := p.storage.UserPut(user); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
package recaptcha
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/securecookie"
|
|
||||||
"github.com/play-with-docker/play-with-docker/config"
|
|
||||||
"github.com/rs/xid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetGoogleRecaptchaSiteKey() string {
|
|
||||||
key := os.Getenv("GOOGLE_RECAPTCHA_SITE_KEY")
|
|
||||||
if key == "" {
|
|
||||||
// This is a development default. The environment variable should always be set in production.
|
|
||||||
key = "6LeY_QsUAAAAAOlpVw4MhoLEr50h-dM80oz6M2AX"
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
func GetGoogleRecaptchaSiteSecret() string {
|
|
||||||
key := os.Getenv("GOOGLE_RECAPTCHA_SITE_SECRET")
|
|
||||||
if key == "" {
|
|
||||||
// This is a development default. The environment variable should always be set in production.
|
|
||||||
key = "6LeY_QsUAAAAAHIALCtm0GKfk-UhtXoyJKarnRV8"
|
|
||||||
}
|
|
||||||
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
type recaptchaResponse struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var s = securecookie.New([]byte(config.HashKey), nil).MaxAge(int((1 * time.Hour).Seconds()))
|
|
||||||
|
|
||||||
func IsHuman(req *http.Request, rw http.ResponseWriter) bool {
|
|
||||||
if os.Getenv("GOOGLE_RECAPTCHA_DISABLED") != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if cookie, _ := req.Cookie("session_id"); cookie != nil {
|
|
||||||
var value string
|
|
||||||
if err := s.Decode("session_id", cookie.Value, &value); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
challenge := req.Form.Get("g-recaptcha-response")
|
|
||||||
|
|
||||||
// Of X-Forwarded-For exists, it means we are behind a loadbalancer and we should use the real IP address of the user
|
|
||||||
ip := req.Header.Get("X-Forwarded-For")
|
|
||||||
if ip == "" {
|
|
||||||
// Use the standard remote IP address of the request
|
|
||||||
|
|
||||||
ip = req.RemoteAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(ip, ":")
|
|
||||||
|
|
||||||
resp, postErr := http.PostForm("https://www.google.com/recaptcha/api/siteverify", url.Values{"secret": {GetGoogleRecaptchaSiteSecret()}, "response": {challenge}, "remoteip": {parts[0]}})
|
|
||||||
if postErr != nil {
|
|
||||||
log.Println(postErr)
|
|
||||||
// If there is a problem to connect to google, assume the user is a human so we don't block real users because of technical issues
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var r recaptchaResponse
|
|
||||||
json.NewDecoder(resp.Body).Decode(&r)
|
|
||||||
|
|
||||||
if !r.Success {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
encoded, _ := s.Encode("session_id", xid.New().String())
|
|
||||||
http.SetCookie(rw, &http.Cookie{
|
|
||||||
Name: "session_id",
|
|
||||||
Value: encoded,
|
|
||||||
Expires: time.Now().Add(1 * time.Hour),
|
|
||||||
})
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -19,10 +20,13 @@ type DB struct {
|
|||||||
Instances map[string]*types.Instance `json:"instances"`
|
Instances map[string]*types.Instance `json:"instances"`
|
||||||
Clients map[string]*types.Client `json:"clients"`
|
Clients map[string]*types.Client `json:"clients"`
|
||||||
WindowsInstances map[string]*types.WindowsInstance `json:"windows_instances"`
|
WindowsInstances map[string]*types.WindowsInstance `json:"windows_instances"`
|
||||||
|
LoginRequests map[string]*types.LoginRequest `json:"login_requests"`
|
||||||
|
Users map[string]*types.User `json:"user"`
|
||||||
|
|
||||||
WindowsInstancesBySessionId map[string][]string `json:"windows_instances_by_session_id"`
|
WindowsInstancesBySessionId map[string][]string `json:"windows_instances_by_session_id"`
|
||||||
InstancesBySessionId map[string][]string `json:"instances_by_session_id"`
|
InstancesBySessionId map[string][]string `json:"instances_by_session_id"`
|
||||||
ClientsBySessionId map[string][]string `json:"clients_by_session_id"`
|
ClientsBySessionId map[string][]string `json:"clients_by_session_id"`
|
||||||
|
UsersByProvider map[string]string `json:"users_by_providers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (store *storage) SessionGet(id string) (*types.Session, error) {
|
func (store *storage) SessionGet(id string) (*types.Session, error) {
|
||||||
@@ -300,6 +304,56 @@ func (store *storage) ClientFindBySessionId(sessionId string) ([]*types.Client,
|
|||||||
return clients, nil
|
return clients, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *storage) LoginRequestPut(loginRequest *types.LoginRequest) error {
|
||||||
|
store.rw.Lock()
|
||||||
|
defer store.rw.Unlock()
|
||||||
|
|
||||||
|
store.db.LoginRequests[loginRequest.Id] = loginRequest
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (store *storage) LoginRequestGet(id string) (*types.LoginRequest, error) {
|
||||||
|
store.rw.Lock()
|
||||||
|
defer store.rw.Unlock()
|
||||||
|
|
||||||
|
if lr, found := store.db.LoginRequests[id]; !found {
|
||||||
|
return nil, NotFoundError
|
||||||
|
} else {
|
||||||
|
return lr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (store *storage) LoginRequestDelete(id string) error {
|
||||||
|
store.rw.Lock()
|
||||||
|
defer store.rw.Unlock()
|
||||||
|
|
||||||
|
delete(store.db.LoginRequests, id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *storage) UserFindByProvider(providerName, providerUserId string) (*types.User, error) {
|
||||||
|
store.rw.Lock()
|
||||||
|
defer store.rw.Unlock()
|
||||||
|
|
||||||
|
if userId, found := store.db.UsersByProvider[fmt.Sprintf("%s_%s", providerName, providerUserId)]; !found {
|
||||||
|
return nil, NotFoundError
|
||||||
|
} else {
|
||||||
|
if user, found := store.db.Users[userId]; !found {
|
||||||
|
return nil, NotFoundError
|
||||||
|
} else {
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *storage) UserPut(user *types.User) error {
|
||||||
|
store.rw.Lock()
|
||||||
|
defer store.rw.Unlock()
|
||||||
|
|
||||||
|
store.db.UsersByProvider[fmt.Sprintf("%s_%s", user.Provider, user.ProviderUserId)] = user.Id
|
||||||
|
store.db.Users[user.Id] = user
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (store *storage) load() error {
|
func (store *storage) load() error {
|
||||||
file, err := os.Open(store.path)
|
file, err := os.Open(store.path)
|
||||||
|
|
||||||
@@ -312,13 +366,16 @@ func (store *storage) load() error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
store.db = &DB{
|
store.db = &DB{
|
||||||
Sessions: map[string]*types.Session{},
|
Sessions: map[string]*types.Session{},
|
||||||
Instances: map[string]*types.Instance{},
|
Instances: map[string]*types.Instance{},
|
||||||
Clients: map[string]*types.Client{},
|
Clients: map[string]*types.Client{},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{},
|
WindowsInstances: map[string]*types.WindowsInstance{},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{},
|
WindowsInstancesBySessionId: map[string][]string{},
|
||||||
InstancesBySessionId: map[string][]string{},
|
InstancesBySessionId: map[string][]string{},
|
||||||
ClientsBySessionId: map[string][]string{},
|
ClientsBySessionId: map[string][]string{},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,13 +30,16 @@ func TestSessionPut(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
expectedDB := &DB{
|
expectedDB := &DB{
|
||||||
Sessions: map[string]*types.Session{s.Id: s},
|
Sessions: map[string]*types.Session{s.Id: s},
|
||||||
Instances: map[string]*types.Instance{},
|
Instances: map[string]*types.Instance{},
|
||||||
Clients: map[string]*types.Client{},
|
Clients: map[string]*types.Client{},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{},
|
WindowsInstances: map[string]*types.WindowsInstance{},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{},
|
WindowsInstancesBySessionId: map[string][]string{},
|
||||||
InstancesBySessionId: map[string][]string{},
|
InstancesBySessionId: map[string][]string{},
|
||||||
ClientsBySessionId: map[string][]string{},
|
ClientsBySessionId: map[string][]string{},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
var loadedDB *DB
|
var loadedDB *DB
|
||||||
|
|
||||||
@@ -56,13 +59,16 @@ func TestSessionPut(t *testing.T) {
|
|||||||
func TestSessionGet(t *testing.T) {
|
func TestSessionGet(t *testing.T) {
|
||||||
expectedSession := &types.Session{Id: "aaabbbccc"}
|
expectedSession := &types.Session{Id: "aaabbbccc"}
|
||||||
expectedDB := &DB{
|
expectedDB := &DB{
|
||||||
Sessions: map[string]*types.Session{expectedSession.Id: expectedSession},
|
Sessions: map[string]*types.Session{expectedSession.Id: expectedSession},
|
||||||
Instances: map[string]*types.Instance{},
|
Instances: map[string]*types.Instance{},
|
||||||
Clients: map[string]*types.Client{},
|
Clients: map[string]*types.Client{},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{},
|
WindowsInstances: map[string]*types.WindowsInstance{},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{},
|
WindowsInstancesBySessionId: map[string][]string{},
|
||||||
InstancesBySessionId: map[string][]string{},
|
InstancesBySessionId: map[string][]string{},
|
||||||
ClientsBySessionId: map[string][]string{},
|
ClientsBySessionId: map[string][]string{},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpfile, err := ioutil.TempFile("", "pwd")
|
tmpfile, err := ioutil.TempFile("", "pwd")
|
||||||
@@ -92,13 +98,16 @@ func TestSessionGetAll(t *testing.T) {
|
|||||||
s1 := &types.Session{Id: "aaabbbccc"}
|
s1 := &types.Session{Id: "aaabbbccc"}
|
||||||
s2 := &types.Session{Id: "dddeeefff"}
|
s2 := &types.Session{Id: "dddeeefff"}
|
||||||
expectedDB := &DB{
|
expectedDB := &DB{
|
||||||
Sessions: map[string]*types.Session{s1.Id: s1, s2.Id: s2},
|
Sessions: map[string]*types.Session{s1.Id: s1, s2.Id: s2},
|
||||||
Instances: map[string]*types.Instance{},
|
Instances: map[string]*types.Instance{},
|
||||||
Clients: map[string]*types.Client{},
|
Clients: map[string]*types.Client{},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{},
|
WindowsInstances: map[string]*types.WindowsInstance{},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{},
|
WindowsInstancesBySessionId: map[string][]string{},
|
||||||
InstancesBySessionId: map[string][]string{},
|
InstancesBySessionId: map[string][]string{},
|
||||||
ClientsBySessionId: map[string][]string{},
|
ClientsBySessionId: map[string][]string{},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpfile, err := ioutil.TempFile("", "pwd")
|
tmpfile, err := ioutil.TempFile("", "pwd")
|
||||||
@@ -154,13 +163,16 @@ func TestSessionDelete(t *testing.T) {
|
|||||||
func TestInstanceGet(t *testing.T) {
|
func TestInstanceGet(t *testing.T) {
|
||||||
expectedInstance := &types.Instance{SessionId: "aaabbbccc", Name: "i1", IP: "10.0.0.1"}
|
expectedInstance := &types.Instance{SessionId: "aaabbbccc", Name: "i1", IP: "10.0.0.1"}
|
||||||
expectedDB := &DB{
|
expectedDB := &DB{
|
||||||
Sessions: map[string]*types.Session{},
|
Sessions: map[string]*types.Session{},
|
||||||
Instances: map[string]*types.Instance{expectedInstance.Name: expectedInstance},
|
Instances: map[string]*types.Instance{expectedInstance.Name: expectedInstance},
|
||||||
Clients: map[string]*types.Client{},
|
Clients: map[string]*types.Client{},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{},
|
WindowsInstances: map[string]*types.WindowsInstance{},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{},
|
WindowsInstancesBySessionId: map[string][]string{},
|
||||||
InstancesBySessionId: map[string][]string{expectedInstance.SessionId: []string{expectedInstance.Name}},
|
InstancesBySessionId: map[string][]string{expectedInstance.SessionId: []string{expectedInstance.Name}},
|
||||||
ClientsBySessionId: map[string][]string{},
|
ClientsBySessionId: map[string][]string{},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpfile, err := ioutil.TempFile("", "pwd")
|
tmpfile, err := ioutil.TempFile("", "pwd")
|
||||||
@@ -205,13 +217,16 @@ func TestInstancePut(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
expectedDB := &DB{
|
expectedDB := &DB{
|
||||||
Sessions: map[string]*types.Session{s.Id: s},
|
Sessions: map[string]*types.Session{s.Id: s},
|
||||||
Instances: map[string]*types.Instance{i.Name: i},
|
Instances: map[string]*types.Instance{i.Name: i},
|
||||||
Clients: map[string]*types.Client{},
|
Clients: map[string]*types.Client{},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{},
|
WindowsInstances: map[string]*types.WindowsInstance{},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{},
|
WindowsInstancesBySessionId: map[string][]string{},
|
||||||
InstancesBySessionId: map[string][]string{i.SessionId: []string{i.Name}},
|
InstancesBySessionId: map[string][]string{i.SessionId: []string{i.Name}},
|
||||||
ClientsBySessionId: map[string][]string{},
|
ClientsBySessionId: map[string][]string{},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
var loadedDB *DB
|
var loadedDB *DB
|
||||||
|
|
||||||
@@ -265,13 +280,16 @@ func TestInstanceFindBySessionId(t *testing.T) {
|
|||||||
i1 := &types.Instance{SessionId: "aaabbbccc", Name: "c1"}
|
i1 := &types.Instance{SessionId: "aaabbbccc", Name: "c1"}
|
||||||
i2 := &types.Instance{SessionId: "aaabbbccc", Name: "c2"}
|
i2 := &types.Instance{SessionId: "aaabbbccc", Name: "c2"}
|
||||||
expectedDB := &DB{
|
expectedDB := &DB{
|
||||||
Sessions: map[string]*types.Session{},
|
Sessions: map[string]*types.Session{},
|
||||||
Instances: map[string]*types.Instance{i1.Name: i1, i2.Name: i2},
|
Instances: map[string]*types.Instance{i1.Name: i1, i2.Name: i2},
|
||||||
Clients: map[string]*types.Client{},
|
Clients: map[string]*types.Client{},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{},
|
WindowsInstances: map[string]*types.WindowsInstance{},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{},
|
WindowsInstancesBySessionId: map[string][]string{},
|
||||||
InstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Name, i2.Name}},
|
InstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Name, i2.Name}},
|
||||||
ClientsBySessionId: map[string][]string{},
|
ClientsBySessionId: map[string][]string{},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpfile, err := ioutil.TempFile("", "pwd")
|
tmpfile, err := ioutil.TempFile("", "pwd")
|
||||||
@@ -298,13 +316,16 @@ func TestWindowsInstanceGetAll(t *testing.T) {
|
|||||||
i1 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i1"}
|
i1 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i1"}
|
||||||
i2 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i2"}
|
i2 := &types.WindowsInstance{SessionId: "aaabbbccc", Id: "i2"}
|
||||||
expectedDB := &DB{
|
expectedDB := &DB{
|
||||||
Sessions: map[string]*types.Session{},
|
Sessions: map[string]*types.Session{},
|
||||||
Instances: map[string]*types.Instance{},
|
Instances: map[string]*types.Instance{},
|
||||||
Clients: map[string]*types.Client{},
|
Clients: map[string]*types.Client{},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{i1.Id: i1, i2.Id: i2},
|
WindowsInstances: map[string]*types.WindowsInstance{i1.Id: i1, i2.Id: i2},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Id, i2.Id}},
|
WindowsInstancesBySessionId: map[string][]string{i1.SessionId: []string{i1.Id, i2.Id}},
|
||||||
InstancesBySessionId: map[string][]string{},
|
InstancesBySessionId: map[string][]string{},
|
||||||
ClientsBySessionId: map[string][]string{},
|
ClientsBySessionId: map[string][]string{},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpfile, err := ioutil.TempFile("", "pwd")
|
tmpfile, err := ioutil.TempFile("", "pwd")
|
||||||
@@ -350,13 +371,16 @@ func TestWindowsInstancePut(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
expectedDB := &DB{
|
expectedDB := &DB{
|
||||||
Sessions: map[string]*types.Session{s.Id: s},
|
Sessions: map[string]*types.Session{s.Id: s},
|
||||||
Instances: map[string]*types.Instance{},
|
Instances: map[string]*types.Instance{},
|
||||||
Clients: map[string]*types.Client{},
|
Clients: map[string]*types.Client{},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{i.Id: i},
|
WindowsInstances: map[string]*types.WindowsInstance{i.Id: i},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{i.SessionId: []string{i.Id}},
|
WindowsInstancesBySessionId: map[string][]string{i.SessionId: []string{i.Id}},
|
||||||
InstancesBySessionId: map[string][]string{},
|
InstancesBySessionId: map[string][]string{},
|
||||||
ClientsBySessionId: map[string][]string{},
|
ClientsBySessionId: map[string][]string{},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
var loadedDB *DB
|
var loadedDB *DB
|
||||||
|
|
||||||
@@ -409,13 +433,16 @@ func TestWindowsInstanceDelete(t *testing.T) {
|
|||||||
func TestClientGet(t *testing.T) {
|
func TestClientGet(t *testing.T) {
|
||||||
c := &types.Client{SessionId: "aaabbbccc", Id: "c1"}
|
c := &types.Client{SessionId: "aaabbbccc", Id: "c1"}
|
||||||
expectedDB := &DB{
|
expectedDB := &DB{
|
||||||
Sessions: map[string]*types.Session{},
|
Sessions: map[string]*types.Session{},
|
||||||
Instances: map[string]*types.Instance{},
|
Instances: map[string]*types.Instance{},
|
||||||
Clients: map[string]*types.Client{c.Id: c},
|
Clients: map[string]*types.Client{c.Id: c},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{},
|
WindowsInstances: map[string]*types.WindowsInstance{},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{},
|
WindowsInstancesBySessionId: map[string][]string{},
|
||||||
InstancesBySessionId: map[string][]string{},
|
InstancesBySessionId: map[string][]string{},
|
||||||
ClientsBySessionId: map[string][]string{c.SessionId: []string{c.Id}},
|
ClientsBySessionId: map[string][]string{c.SessionId: []string{c.Id}},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpfile, err := ioutil.TempFile("", "pwd")
|
tmpfile, err := ioutil.TempFile("", "pwd")
|
||||||
@@ -460,13 +487,16 @@ func TestClientPut(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
expectedDB := &DB{
|
expectedDB := &DB{
|
||||||
Sessions: map[string]*types.Session{s.Id: s},
|
Sessions: map[string]*types.Session{s.Id: s},
|
||||||
Instances: map[string]*types.Instance{},
|
Instances: map[string]*types.Instance{},
|
||||||
Clients: map[string]*types.Client{c.Id: c},
|
Clients: map[string]*types.Client{c.Id: c},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{},
|
WindowsInstances: map[string]*types.WindowsInstance{},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{},
|
WindowsInstancesBySessionId: map[string][]string{},
|
||||||
InstancesBySessionId: map[string][]string{},
|
InstancesBySessionId: map[string][]string{},
|
||||||
ClientsBySessionId: map[string][]string{c.SessionId: []string{c.Id}},
|
ClientsBySessionId: map[string][]string{c.SessionId: []string{c.Id}},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
var loadedDB *DB
|
var loadedDB *DB
|
||||||
|
|
||||||
@@ -520,13 +550,16 @@ func TestClientFindBySessionId(t *testing.T) {
|
|||||||
c1 := &types.Client{SessionId: "aaabbbccc", Id: "c1"}
|
c1 := &types.Client{SessionId: "aaabbbccc", Id: "c1"}
|
||||||
c2 := &types.Client{SessionId: "aaabbbccc", Id: "c2"}
|
c2 := &types.Client{SessionId: "aaabbbccc", Id: "c2"}
|
||||||
expectedDB := &DB{
|
expectedDB := &DB{
|
||||||
Sessions: map[string]*types.Session{},
|
Sessions: map[string]*types.Session{},
|
||||||
Instances: map[string]*types.Instance{},
|
Instances: map[string]*types.Instance{},
|
||||||
Clients: map[string]*types.Client{c1.Id: c1, c2.Id: c2},
|
Clients: map[string]*types.Client{c1.Id: c1, c2.Id: c2},
|
||||||
WindowsInstances: map[string]*types.WindowsInstance{},
|
WindowsInstances: map[string]*types.WindowsInstance{},
|
||||||
|
LoginRequests: map[string]*types.LoginRequest{},
|
||||||
|
Users: map[string]*types.User{},
|
||||||
WindowsInstancesBySessionId: map[string][]string{},
|
WindowsInstancesBySessionId: map[string][]string{},
|
||||||
InstancesBySessionId: map[string][]string{},
|
InstancesBySessionId: map[string][]string{},
|
||||||
ClientsBySessionId: map[string][]string{c1.SessionId: []string{c1.Id, c2.Id}},
|
ClientsBySessionId: map[string][]string{c1.SessionId: []string{c1.Id, c2.Id}},
|
||||||
|
UsersByProvider: map[string]string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpfile, err := ioutil.TempFile("", "pwd")
|
tmpfile, err := ioutil.TempFile("", "pwd")
|
||||||
|
|||||||
@@ -82,3 +82,23 @@ func (m *Mock) ClientFindBySessionId(sessionId string) ([]*types.Client, error)
|
|||||||
args := m.Called(sessionId)
|
args := m.Called(sessionId)
|
||||||
return args.Get(0).([]*types.Client), args.Error(1)
|
return args.Get(0).([]*types.Client), args.Error(1)
|
||||||
}
|
}
|
||||||
|
func (m *Mock) LoginRequestPut(loginRequest *types.LoginRequest) error {
|
||||||
|
args := m.Called(loginRequest)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
func (m *Mock) LoginRequestGet(id string) (*types.LoginRequest, error) {
|
||||||
|
args := m.Called(id)
|
||||||
|
return args.Get(0).(*types.LoginRequest), args.Error(1)
|
||||||
|
}
|
||||||
|
func (m *Mock) LoginRequestDelete(id string) error {
|
||||||
|
args := m.Called(id)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
func (m *Mock) UserFindByProvider(providerName, providerUserId string) (*types.User, error) {
|
||||||
|
args := m.Called(providerName, providerUserId)
|
||||||
|
return args.Get(0).(*types.User), args.Error(1)
|
||||||
|
}
|
||||||
|
func (m *Mock) UserPut(user *types.User) error {
|
||||||
|
args := m.Called(user)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,4 +34,11 @@ type StorageApi interface {
|
|||||||
ClientDelete(id string) error
|
ClientDelete(id string) error
|
||||||
ClientCount() (int, error)
|
ClientCount() (int, error)
|
||||||
ClientFindBySessionId(sessionId string) ([]*types.Client, error)
|
ClientFindBySessionId(sessionId string) ([]*types.Client, error)
|
||||||
|
|
||||||
|
LoginRequestPut(loginRequest *types.LoginRequest) error
|
||||||
|
LoginRequestGet(id string) (*types.LoginRequest, error)
|
||||||
|
LoginRequestDelete(id string) error
|
||||||
|
|
||||||
|
UserFindByProvider(providerName, providerUserId string) (*types.User, error)
|
||||||
|
UserPut(user *types.User) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package templates
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"html/template"
|
|
||||||
|
|
||||||
"github.com/play-with-docker/play-with-docker/recaptcha"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetWelcomeTemplate() ([]byte, error) {
|
|
||||||
welcomeTemplate, tplErr := template.New("welcome").ParseFiles("www/welcome.html")
|
|
||||||
if tplErr != nil {
|
|
||||||
return nil, tplErr
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
tplExecuteErr := welcomeTemplate.ExecuteTemplate(&b, "GOOGLE_RECAPTCHA_SITE_KEY", recaptcha.GetGoogleRecaptchaSiteKey())
|
|
||||||
if tplExecuteErr != nil {
|
|
||||||
return nil, tplExecuteErr
|
|
||||||
}
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$scope.showAlert = function(title, content, parent) {
|
$scope.showAlert = function(title, content, parent, cb) {
|
||||||
$mdDialog.show(
|
$mdDialog.show(
|
||||||
$mdDialog.alert()
|
$mdDialog.alert()
|
||||||
.parent(angular.element(document.querySelector(parent || '#popupContainer')))
|
.parent(angular.element(document.querySelector(parent || '#popupContainer')))
|
||||||
@@ -86,7 +86,11 @@
|
|||||||
.title(title)
|
.title(title)
|
||||||
.textContent(content)
|
.textContent(content)
|
||||||
.ok('Got it!')
|
.ok('Got it!')
|
||||||
);
|
).finally(function() {
|
||||||
|
if (cb) {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.resize = function(geometry) {
|
$scope.resize = function(geometry) {
|
||||||
@@ -206,7 +210,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('session end', function() {
|
socket.on('session end', function() {
|
||||||
$scope.showAlert('Session timed out!', 'Your session has expired and all of your instances have been deleted.', '#sessionEnd')
|
$scope.showAlert('Session timed out!', 'Your session has expired and all of your instances have been deleted.', '#sessionEnd', function() {
|
||||||
|
window.location.href = '/';
|
||||||
|
});
|
||||||
$scope.isAlive = false;
|
$scope.isAlive = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
79
www/assets/landing.css
Normal file
79
www/assets/landing.css
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/* Space out content a bit */
|
||||||
|
body {
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Everything but the jumbotron gets side spacing for mobile first views */
|
||||||
|
.header,
|
||||||
|
.marketing,
|
||||||
|
.footer {
|
||||||
|
padding-right: 1rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom page header */
|
||||||
|
.header {
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: .05rem solid #e5e5e5;
|
||||||
|
}
|
||||||
|
/* Make the masthead heading the same height as the navigation */
|
||||||
|
.header h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
line-height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom page footer */
|
||||||
|
.footer {
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
color: #777;
|
||||||
|
border-top: .05rem solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Customize container */
|
||||||
|
@media (min-width: 48em) {
|
||||||
|
.container {
|
||||||
|
max-width: 46rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.container-narrow > hr {
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main marketing message and sign up button */
|
||||||
|
.jumbotron {
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: .05rem solid #e5e5e5;
|
||||||
|
}
|
||||||
|
.jumbotron .btn {
|
||||||
|
padding: .75rem 1.5rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Supporting marketing content */
|
||||||
|
.marketing {
|
||||||
|
margin: 3rem 0;
|
||||||
|
}
|
||||||
|
.marketing p + h4 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive: Portrait tablets and up */
|
||||||
|
@media screen and (min-width: 48em) {
|
||||||
|
/* Remove the padding we set earlier */
|
||||||
|
.header,
|
||||||
|
.marketing,
|
||||||
|
.footer {
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
/* Space out the masthead */
|
||||||
|
.header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
/* Remove the bottom border on the jumbotron for visual effect */
|
||||||
|
.jumbotron {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
116
www/landing.html
Normal file
116
www/landing.html
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" ng-app="PWDLanding" ng-controller="LoginController">
|
||||||
|
<head>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular-cookies.js"></script>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
|
||||||
|
<title>Play with Docker</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap core CSS -->
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<!-- Custom styles for this template -->
|
||||||
|
<link href="/assets/landing.css" rel="stylesheet">
|
||||||
|
<script>
|
||||||
|
angular.module('PWDLanding', ['ngCookies'])
|
||||||
|
.controller('LoginController', ['$cookies', '$scope', '$http', function($cookies, $scope, $http) {
|
||||||
|
$scope.providers = [];
|
||||||
|
$scope.loggedIn = $cookies.get('id') !== undefined;
|
||||||
|
|
||||||
|
$http({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/oauth/providers'
|
||||||
|
}).then(function(response) {
|
||||||
|
$scope.providers = response.data;
|
||||||
|
if ($scope.providers.length == 0) {
|
||||||
|
$scope.loggedIn = true;
|
||||||
|
}
|
||||||
|
}, function(response) {
|
||||||
|
console.log('ERROR', response);
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.start = function() {
|
||||||
|
function getParameterByName(name, url) {
|
||||||
|
if (!url) url = window.location.href;
|
||||||
|
name = name.replace(/[\[\]]/g, "\\$&");
|
||||||
|
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||||
|
results = regex.exec(url);
|
||||||
|
if (!results) return null;
|
||||||
|
if (!results[2]) return '';
|
||||||
|
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
var stack = getParameterByName('stack');
|
||||||
|
if (stack) {
|
||||||
|
document.getElementById('stack').value = stack;
|
||||||
|
}
|
||||||
|
var stackName = getParameterByName('stack_name');
|
||||||
|
if (stackName) {
|
||||||
|
document.getElementById('stack_name').value = stackName;
|
||||||
|
}
|
||||||
|
var imageName = getParameterByName('image_name');
|
||||||
|
if (imageName) {
|
||||||
|
document.getElementById('image_name').value = imageName;
|
||||||
|
}
|
||||||
|
document.getElementById('landingForm').submit();
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="header clearfix">
|
||||||
|
<nav>
|
||||||
|
<ul class="nav nav-pills float-right">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="https://github.com/play-with-docker/play-with-docker">Contribute</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="jumbotron" ng-cloak>
|
||||||
|
<img src="https://www.docker.com/sites/default/files/Whale%20Logo332_5.png" />
|
||||||
|
<h1 class="display-3">Play with Docker</h1>
|
||||||
|
<p class="lead">A simple, interactive and fun playground to learn Docker</p>
|
||||||
|
<div ng-hide="loggedIn" class="btn-group" role="group">
|
||||||
|
<button id="btnGroupDrop1" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
||||||
|
<a ng-repeat="provider in providers" class="dropdown-item" href="/oauth/providers/{{provider}}/login">{{provider}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="landingForm" method="POST" action="/">
|
||||||
|
<p ng-show="loggedIn"><a class="btn btn-lg btn-success" href="#" ng-click="start()" role="button">Start</a></p>
|
||||||
|
<input id="stack" type="hidden" name="stack" value=""/>
|
||||||
|
<input id="stack_name" type="hidden" name="stack_name" value=""/>
|
||||||
|
<input id="image_name" type="hidden" name="image_name" value=""/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row marketing">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<p>Play with Docker (PWD) is a project hacked by <a href="https://www.twitter.com/marcosnils">Marcos Liljedhal</a> and <a href="https://www.twitter.com/xetorthio">Jonathan Leibiusky</a> and sponsored by Docker Inc.</p>
|
||||||
|
<p>PWD is a Docker playground which allows users to run Docker commands in a matter of seconds. It gives the experience of having a free Alpine Linux Virtual Machine in browser, where you can build and run Docker containers and even create clusters in <a href="https://docs.docker.com/engine/swarm/">Docker Swarm Mode</a>. Under the hood Docker-in-Docker (DinD) is used to give the effect of multiple VMs/PCs. In addition to the playground, PWD also includes a training site composed of a large set of Docker labs and quizzes from beginner to advanced level available at <a href="http://training.play-with-docker.com/">training.play-with-docker.com</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<p>© Play with Docker 2017</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
{{define "GOOGLE_RECAPTCHA_SITE_KEY"}}
|
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Docker Playground</title>
|
|
||||||
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.css">
|
|
||||||
<link rel="stylesheet" href="/assets/style.css" />
|
|
||||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
|
||||||
</head>
|
|
||||||
<body class="welcome">
|
|
||||||
<div>
|
|
||||||
<h1>Welcome!</h1>
|
|
||||||
<h2>Before starting we need to verify you are a human</h2>
|
|
||||||
<form id="welcomeForm" method="POST" action="/">
|
|
||||||
<div id="recaptcha" class="g-recaptcha" data-callback="iAmHuman" data-sitekey="{{.}}"></div>
|
|
||||||
<input id="stack" type="hidden" name="stack" value=""/>
|
|
||||||
<input id="stack_name" type="hidden" name="stack_name" value=""/>
|
|
||||||
<input id="image_name" type="hidden" name="image_name" value=""/>
|
|
||||||
<button id="create" style="display:none;">Create session</button>
|
|
||||||
</form>
|
|
||||||
<img src="/assets/full_horizontal.svg" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function iAmHuman(resp) {
|
|
||||||
document.getElementById('welcomeForm').submit();
|
|
||||||
}
|
|
||||||
function getParameterByName(name, url) {
|
|
||||||
if (!url) url = window.location.href;
|
|
||||||
name = name.replace(/[\[\]]/g, "\\$&");
|
|
||||||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
|
||||||
results = regex.exec(url);
|
|
||||||
if (!results) return null;
|
|
||||||
if (!results[2]) return '';
|
|
||||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
|
||||||
}
|
|
||||||
|
|
||||||
var stack = getParameterByName('stack');
|
|
||||||
if (stack) {
|
|
||||||
document.getElementById('stack').value = stack;
|
|
||||||
}
|
|
||||||
var stackName = getParameterByName('stack_name');
|
|
||||||
if (stackName) {
|
|
||||||
document.getElementById('stack_name').value = stackName;
|
|
||||||
}
|
|
||||||
var imageName = getParameterByName('image_name');
|
|
||||||
if (imageName) {
|
|
||||||
document.getElementById('image_name').value = imageName;
|
|
||||||
}
|
|
||||||
if (document.cookie.indexOf('session_id') > -1) {
|
|
||||||
document.getElementById('create').style = "";
|
|
||||||
document.getElementById('recaptcha').style = "display:none;";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
{{end}}
|
|
||||||
Reference in New Issue
Block a user