package handlers import ( "encoding/json" "fmt" "log" "net/http" "strconv" "strings" "golang.org/x/oauth2" "github.com/google/go-github/github" "github.com/gorilla/mux" "github.com/play-with-docker/play-with-docker/config" "github.com/play-with-docker/play-with-docker/pwd/types" uuid "github.com/satori/go.uuid" "google.golang.org/api/people/v1" ) func LoggedInUser(rw http.ResponseWriter, req *http.Request) { cookie, err := ReadCookie(req) if err != nil { log.Println("Cannot read cookie") rw.WriteHeader(http.StatusUnauthorized) return } user, err := core.UserGet(cookie.Id) if err != nil { log.Printf("Couldn't get user with id %s. Got: %v\n", cookie.Id, err) rw.WriteHeader(http.StatusUnauthorized) return } json.NewEncoder(rw).Encode(user) } func ListProviders(rw http.ResponseWriter, req *http.Request) { playground := core.PlaygroundFindByDomain(req.Host) if playground == nil { log.Printf("Playground for domain %s was not found!", req.Host) rw.WriteHeader(http.StatusBadRequest) return } providers := []string{} for name := range config.Providers[playground.Id] { providers = append(providers, name) } json.NewEncoder(rw).Encode(providers) } func Login(rw http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) providerName := vars["provider"] playground := core.PlaygroundFindByDomain(req.Host) if playground == nil { log.Printf("Playground for domain %s was not found!", req.Host) rw.WriteHeader(http.StatusBadRequest) return } provider, found := config.Providers[playground.Id][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.TLS != nil { scheme = "https" } host := "localhost" if req.Host != "" { host = req.Host } provider.RedirectURL = fmt.Sprintf("%s://%s/oauth/providers/%s/callback", scheme, host, providerName) url := provider.AuthCodeURL(loginRequest.Id, oauth2.SetAuthURLParam("nonce", uuid.NewV4().String())) http.Redirect(rw, req, url, http.StatusFound) } func LoginCallback(rw http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) providerName := vars["provider"] playground := core.PlaygroundFindByDomain(req.Host) if playground == nil { log.Printf("Playground for domain %s was not found!", req.Host) rw.WriteHeader(http.StatusBadRequest) return } provider, found := config.Providers[playground.Id][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 == "google" { ts := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: tok.AccessToken}, ) tc := oauth2.NewClient(ctx, ts) p, err := people.New(tc) if err != nil { log.Printf("Could not initialize people service . Got: %v\n", err) rw.WriteHeader(http.StatusInternalServerError) return } person, err := p.People.Get("people/me").PersonFields("emailAddresses,names").Do() if err != nil { log.Printf("Could not initialize people service . Got: %v\n", err) rw.WriteHeader(http.StatusInternalServerError) return } user.Email = person.EmailAddresses[0].Value user.Name = person.Names[0].GivenName user.ProviderUserId = person.ResourceName } else if providerName == "docker" { ts := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: tok.AccessToken}, ) tc := oauth2.NewClient(ctx, ts) endpoint := getDockerEndpoint(playground) resp, err := tc.Get(fmt.Sprintf("https://%s/api/id/v1/openid/userinfo", endpoint)) if err != nil { log.Printf("Could not get user from docker. Got: %v\n", err) rw.WriteHeader(http.StatusInternalServerError) return } userInfo := map[string]string{} if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil { log.Printf("Could not decode user info. Got: %v\n", err) rw.WriteHeader(http.StatusInternalServerError) return } user.ProviderUserId = userInfo["sub"] user.Name = userInfo["preferred_username"] user.Email = userInfo["email"] // Since DockerID doesn't return a user avatar, we try with twitter through avatars.io // Worst case we get a generic avatar user.Avatar = fmt.Sprintf("https://avatars.io/twitter/%s", user.Name) } 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, ProviderId: user.ProviderUserId} host := "localhost" if req.Host != "" { // we get the parent domain so cookie is set // in all subdomain and siblings host = getParentDomain(req.Host) } if err := cookieData.SetCookie(rw, host); err != nil { log.Printf("Could not encode cookie. Got: %v\n", err) rw.WriteHeader(http.StatusInternalServerError) return } r, _ := playground.Extras.GetString("LoginRedirect") fmt.Fprintf(rw, `
`, r) } // getParentDomain returns the parent domain (if available) // of the currend domain func getParentDomain(host string) string { levels := strings.Split(host, ".") if len(levels) > 2 { return strings.Join(levels[1:], ".") } return host } func getDockerEndpoint(p *types.Playground) string { if len(p.DockerHost) > 0 { return p.DockerHost } return "id.docker.com" }