wide/session/users.go

496 lines
12 KiB
Go
Raw Normal View History

2017-03-14 17:40:45 +03:00
// Copyright (c) 2014-2017, b3log.org & hacpai.com
2014-11-17 06:20:35 +03:00
//
2014-11-12 18:13:14 +03:00
// 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
2014-11-17 06:20:35 +03:00
//
2014-11-12 18:13:14 +03:00
// http://www.apache.org/licenses/LICENSE-2.0
2014-11-17 06:20:35 +03:00
//
2014-11-12 18:13:14 +03:00
// 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.
2014-09-17 10:35:48 +04:00
package session
2014-08-31 14:50:38 +04:00
import (
2014-12-14 07:27:35 +03:00
"crypto/md5"
"encoding/hex"
2014-08-31 14:50:38 +04:00
"encoding/json"
"math/rand"
2014-09-12 13:10:58 +04:00
"net/http"
"os"
"path/filepath"
2014-11-17 05:49:25 +03:00
"runtime"
"strconv"
2015-01-17 06:15:34 +03:00
"strings"
2014-11-20 06:17:43 +03:00
"sync"
2014-11-30 05:18:17 +03:00
"text/template"
"time"
2014-09-12 13:10:58 +04:00
2014-08-31 14:50:38 +04:00
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
2014-09-01 16:50:51 +04:00
"github.com/b3log/wide/util"
2014-08-31 14:50:38 +04:00
)
const (
2014-12-08 09:02:39 +03:00
// TODO: i18n
2015-03-15 07:11:23 +03:00
userExists = "user exists"
emailExists = "email exists"
userCreated = "user created"
userCreateError = "user create error"
notAllowRegister = "not allow register"
2014-08-31 14:50:38 +04:00
)
2014-11-20 06:17:43 +03:00
// Exclusive lock for adding user.
var addUserMutex sync.Mutex
2014-12-07 06:07:32 +03:00
// PreferenceHandler handles request of preference page.
2014-11-17 05:49:25 +03:00
func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
2016-02-26 08:45:54 +03:00
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound)
2014-11-17 05:49:25 +03:00
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
2014-12-11 10:59:58 +03:00
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
2014-11-17 05:49:25 +03:00
httpSession.Save(r, w)
username := httpSession.Values["username"].(string)
user := conf.GetUser(username)
2014-11-17 05:49:25 +03:00
if "GET" == r.Method {
2017-03-29 07:56:57 +03:00
tmpLinux := user.GoBuildArgsForLinux
tmpWindows := user.GoBuildArgsForWindows
tmpDarwin := user.GoBuildArgsForDarwin
user.GoBuildArgsForLinux = strings.Replace(user.GoBuildArgsForLinux, `"`, `"`, -1)
user.GoBuildArgsForWindows = strings.Replace(user.GoBuildArgsForWindows, `"`, `"`, -1)
user.GoBuildArgsForDarwin = strings.Replace(user.GoBuildArgsForDarwin, `"`, `"`, -1)
2014-11-17 05:49:25 +03:00
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(user.Locale), "user": user,
2014-11-26 06:24:24 +03:00
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version(),
2014-11-30 05:18:17 +03:00
"locales": i18n.GetLocalesNames(), "gofmts": util.Go.GetGoFormats(),
"themes": conf.GetThemes(), "editorThemes": conf.GetEditorThemes()}
2014-11-17 06:20:35 +03:00
2014-11-17 05:49:25 +03:00
t, err := template.ParseFiles("views/preference.html")
if nil != err {
2014-12-13 13:47:41 +03:00
logger.Error(err)
2014-11-17 05:49:25 +03:00
http.Error(w, err.Error(), 500)
2017-03-29 07:56:57 +03:00
user.GoBuildArgsForLinux = tmpLinux
user.GoBuildArgsForWindows = tmpWindows
user.GoBuildArgsForDarwin = tmpDarwin
2014-11-17 05:49:25 +03:00
return
}
t.Execute(w, model)
2017-03-29 07:56:57 +03:00
user.GoBuildArgsForLinux = tmpLinux
user.GoBuildArgsForWindows = tmpWindows
user.GoBuildArgsForDarwin = tmpDarwin
2014-11-17 06:20:35 +03:00
2014-11-17 05:49:25 +03:00
return
}
2014-11-17 06:20:35 +03:00
2014-11-17 05:49:25 +03:00
// non-GET request as save request
2014-11-17 06:20:35 +03:00
2015-11-24 12:39:35 +03:00
result := util.NewResult()
defer util.RetResult(w, r, result)
2014-11-17 06:20:35 +03:00
args := struct {
FontFamily string
FontSize string
GoFmt string
GoBuildArgsForLinux string
GoBuildArgsForWindows string
GoBuildArgsForDarwin string
Keymap string
Workspace string
Username string
Password string
Email string
Locale string
Theme string
EditorFontFamily string
EditorFontSize string
EditorLineHeight string
EditorTheme string
EditorTabSize string
2014-11-17 06:20:35 +03:00
}{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
2014-12-13 13:47:41 +03:00
logger.Error(err)
2015-11-24 12:39:35 +03:00
result.Succ = false
2014-11-17 06:20:35 +03:00
return
}
user.FontFamily = args.FontFamily
user.FontSize = args.FontSize
user.GoFormat = args.GoFmt
user.GoBuildArgsForLinux = args.GoBuildArgsForLinux
user.GoBuildArgsForWindows = args.GoBuildArgsForWindows
user.GoBuildArgsForDarwin = args.GoBuildArgsForDarwin
2015-05-11 10:46:17 +03:00
user.Keymap = args.Keymap
// XXX: disallow change workspace at present
// user.Workspace = args.Workspace
2014-12-25 17:13:47 +03:00
if user.Password != args.Password {
user.Password = conf.Salt(args.Password, user.Salt)
}
2014-12-08 09:02:39 +03:00
user.Email = args.Email
2014-12-14 07:27:35 +03:00
hash := md5.New()
2014-12-14 05:36:25 +03:00
hash.Write([]byte(user.Email))
2014-12-14 07:27:35 +03:00
user.Gravatar = hex.EncodeToString(hash.Sum(nil))
2014-11-17 06:56:26 +03:00
user.Locale = args.Locale
2014-11-30 05:18:17 +03:00
user.Theme = args.Theme
user.Editor.FontFamily = args.EditorFontFamily
user.Editor.FontSize = args.EditorFontSize
user.Editor.LineHeight = args.EditorLineHeight
user.Editor.Theme = args.EditorTheme
2014-12-01 09:49:16 +03:00
user.Editor.TabSize = args.EditorTabSize
2014-11-17 06:20:35 +03:00
2014-11-20 06:17:43 +03:00
conf.UpdateCustomizedConf(username)
2014-11-30 05:18:17 +03:00
2014-12-23 19:14:03 +03:00
now := time.Now().UnixNano()
user.Lived = now
user.Updated = now
2015-11-24 12:39:35 +03:00
result.Succ = user.Save()
2014-11-17 05:49:25 +03:00
}
// LoginHandler handles request of user login.
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method {
// show the login page
2015-01-02 12:56:13 +03:00
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
2015-01-01 04:24:16 +03:00
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "year": time.Now().Year()}
t, err := template.ParseFiles("views/login.html")
if nil != err {
2014-12-13 13:47:41 +03:00
logger.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
return
}
// non-GET request as login request
2015-10-20 13:08:48 +03:00
result := util.NewResult()
defer util.RetResult(w, r, result)
2014-09-01 16:50:51 +04:00
args := struct {
Username string
Password string
}{}
2014-08-31 14:50:38 +04:00
2015-03-21 13:10:53 +03:00
args.Username = r.FormValue("username")
args.Password = r.FormValue("password")
2014-08-31 14:50:38 +04:00
2015-10-20 13:08:48 +03:00
result.Succ = false
for _, user := range conf.Users {
2014-12-23 19:14:03 +03:00
if user.Name == args.Username && user.Password == conf.Salt(args.Password, user.Salt) {
2015-10-20 13:08:48 +03:00
result.Succ = true
2014-11-17 06:20:35 +03:00
break
}
}
2014-08-31 14:50:38 +04:00
2015-10-20 13:08:48 +03:00
if !result.Succ {
return
2014-08-31 14:50:38 +04:00
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Values["username"] = args.Username
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
2014-12-11 10:59:58 +03:00
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
2014-12-16 06:45:19 +03:00
logger.Debugf("Created a HTTP session [%s] for user [%s]", httpSession.Values["id"].(string), args.Username)
2014-08-31 14:50:38 +04:00
}
// LogoutHandler handles request of user logout (exit).
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
2015-11-24 12:39:35 +03:00
result := util.NewResult()
defer util.RetResult(w, r, result)
2014-09-01 16:50:51 +04:00
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Options.MaxAge = -1
httpSession.Save(r, w)
}
2015-03-09 09:16:46 +03:00
// SignUpUserHandler handles request of registering user.
func SignUpUserHandler(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method {
// show the user sign up page
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
2016-12-14 13:15:53 +03:00
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "dir": conf.Wide.UsersWorkspaces,
2015-01-01 04:24:16 +03:00
"pathSeparator": conf.PathSeparator, "year": time.Now().Year()}
t, err := template.ParseFiles("views/sign_up.html")
if nil != err {
2014-12-13 13:47:41 +03:00
logger.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
return
}
// non-GET request as add user request
2015-11-24 12:39:35 +03:00
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
2014-08-31 14:50:38 +04:00
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
2014-12-13 13:47:41 +03:00
logger.Error(err)
2015-11-24 12:39:35 +03:00
result.Succ = false
2014-08-31 14:50:38 +04:00
return
}
2014-08-31 14:50:38 +04:00
username := args["username"].(string)
password := args["password"].(string)
2014-12-08 09:02:39 +03:00
email := args["email"].(string)
2014-12-08 09:02:39 +03:00
msg := addUser(username, password, email)
2014-12-07 06:07:32 +03:00
if userCreated != msg {
2015-11-24 12:39:35 +03:00
result.Succ = false
result.Msg = msg
2015-01-02 12:56:13 +03:00
return
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Values["username"] = username
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
2015-01-02 12:56:13 +03:00
httpSession.Save(r, w)
2014-08-31 14:50:38 +04:00
}
// FixedTimeSave saves online users' configurations periodically (1 minute).
//
// Main goal of this function is to save user session content, for restoring session content while user open Wide next time.
func FixedTimeSave() {
go func() {
2015-03-16 06:24:55 +03:00
defer util.Recover()
for _ = range time.Tick(time.Minute) {
users := getOnlineUsers()
for _, u := range users {
if u.Save() {
2014-12-23 10:43:30 +03:00
logger.Tracef("Saved online user [%s]'s configurations", u.Name)
}
}
}
}()
}
2015-07-23 11:31:37 +03:00
// CanAccess determines whether the user specified by the given username can access the specified path.
func CanAccess(username, path string) bool {
path = filepath.FromSlash(path)
userWorkspace := conf.GetUserWorkspace(username)
workspaces := filepath.SplitList(userWorkspace)
for _, workspace := range workspaces {
if strings.HasPrefix(path, workspace) {
return true
}
}
return false
}
func getOnlineUsers() []*conf.User {
ret := []*conf.User{}
usernames := map[string]string{} // distinct username
for _, s := range WideSessions {
2014-12-19 12:20:04 +03:00
usernames[s.Username] = s.Username
}
for _, username := range usernames {
u := conf.GetUser(username)
2015-02-13 04:59:51 +03:00
if "playground" == username { // user [playground] is a reserved mock user
continue
}
if nil == u {
logger.Warnf("Not found user [%s]", username)
continue
}
ret = append(ret, u)
}
return ret
}
2014-12-08 09:02:39 +03:00
// addUser add a user with the specified username, password and email.
//
// 1. create the user's workspace
2015-03-15 07:11:23 +03:00
// 2. generate 'Hello, 世界' demo code in the workspace (a console version and a HTTP version)
// 3. update the user customized configurations, such as style.css
// 4. serve files of the user's workspace via HTTP
2015-02-13 04:59:51 +03:00
//
// Note: user [playground] is a reserved mock user
2014-12-08 09:02:39 +03:00
func addUser(username, password, email string) string {
2015-03-15 07:11:23 +03:00
if !conf.Wide.AllowRegister {
return notAllowRegister
}
2015-02-13 04:59:51 +03:00
if "playground" == username {
return userExists
}
2014-11-20 06:17:43 +03:00
addUserMutex.Lock()
defer addUserMutex.Unlock()
2014-11-30 05:18:17 +03:00
for _, user := range conf.Users {
2015-01-17 06:15:34 +03:00
if strings.ToLower(user.Name) == strings.ToLower(username) {
2014-12-07 06:07:32 +03:00
return userExists
2014-08-31 14:50:38 +04:00
}
2014-12-08 09:02:39 +03:00
2015-01-17 06:15:34 +03:00
if strings.ToLower(user.Email) == strings.ToLower(email) {
2014-12-08 09:02:39 +03:00
return emailExists
}
2014-08-31 14:50:38 +04:00
}
2016-12-14 13:15:53 +03:00
workspace := filepath.Join(conf.Wide.UsersWorkspaces, username)
2014-12-08 09:02:39 +03:00
newUser := conf.NewUser(username, password, email, workspace)
conf.Users = append(conf.Users, newUser)
2014-08-31 14:50:38 +04:00
2014-12-19 12:44:50 +03:00
if !newUser.Save() {
2014-12-07 06:07:32 +03:00
return userCreateError
2014-08-31 14:50:38 +04:00
}
conf.CreateWorkspaceDir(workspace)
helloWorld(workspace)
2014-11-01 08:35:02 +03:00
conf.UpdateCustomizedConf(username)
http.Handle("/workspace/"+username+"/",
http.StripPrefix("/workspace/"+username+"/", http.FileServer(http.Dir(newUser.GetWorkspace()))))
2014-12-13 13:47:41 +03:00
logger.Infof("Created a user [%s]", username)
2014-08-31 14:50:38 +04:00
2014-12-07 06:07:32 +03:00
return userCreated
2014-08-31 14:50:38 +04:00
}
2014-12-25 18:59:32 +03:00
// helloWorld generates the 'Hello, 世界' source code.
// 1. src/hello/main.go
// 2. src/web/main.go
func helloWorld(workspace string) {
2014-12-25 18:59:32 +03:00
consoleHello(workspace)
webHello(workspace)
}
func consoleHello(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "hello"
if err := os.MkdirAll(dir, 0755); nil != err {
2014-12-13 13:47:41 +03:00
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
2014-12-13 13:47:41 +03:00
logger.Error(err)
2014-12-25 18:59:32 +03:00
return
}
2015-02-13 04:59:51 +03:00
fout.WriteString(conf.HelloWorld)
fout.Close()
}
2014-12-25 18:59:32 +03:00
func webHello(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "web"
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
logger.Error(err)
return
}
code := `package main
import (
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, 世界"))
})
port := getPort()
// you may need to change the address
fmt.Println("Open http://wide.b3log.org:" + port + " in your browser to see the result")
if err := http.ListenAndServe(":"+port, nil); nil != err {
fmt.Println(err)
}
}
func getPort() string {
rand.Seed(time.Now().UnixNano())
return strconv.Itoa(7000 + rand.Intn(8000-7000))
}
`
fout.WriteString(code)
fout.Close()
}