├── .gitignore ├── README.md ├── bin ├── adduser.go └── aproxy.go ├── conf ├── aproxy.toml └── conf.go ├── doc └── img │ ├── authority.png │ ├── backend.png │ └── role.png ├── go.mod ├── go.sum ├── install.sh ├── lib ├── auditlog │ └── log.go ├── crypto │ ├── AUTHORS │ ├── bcrypt │ │ ├── base64.go │ │ ├── bcrypt.go │ │ └── bcrypt_test.go │ └── blowfish │ │ ├── block.go │ │ ├── blowfish_test.go │ │ ├── cipher.go │ │ └── const.go ├── rfweb │ ├── app.go │ ├── resource.go │ ├── route.go │ ├── route_test.go │ └── session │ │ ├── session.go │ │ └── session_storage.go └── util │ └── util.go ├── loginservices └── github │ └── oauth.go ├── module ├── auth │ ├── auth.go │ ├── authority.go │ ├── login │ │ ├── login.go │ │ ├── logout.go │ │ └── oauth.go │ ├── role.go │ ├── role_test.go │ └── user.go ├── backend_conf │ └── conf_storage.go ├── constant │ └── base.go ├── db │ ├── db.go │ └── mongodb.go ├── oauth │ └── oauth.go ├── proxy │ ├── backend.go │ └── proxy.go └── setting │ ├── authority.go │ ├── backend_conf.go │ ├── base.go │ ├── role.go │ └── user.go ├── release.sh └── web └── static ├── css └── base.css ├── index.html ├── js ├── app.js ├── controllers.js ├── directives.js ├── filters.js ├── login.js └── services.js ├── lib ├── angular-animate │ ├── .bower.json │ ├── README.md │ ├── angular-animate.js │ ├── angular-animate.min.js │ ├── angular-animate.min.js.map │ ├── bower.json │ ├── index.js │ └── package.json ├── angular-mocks │ ├── .bower.json │ ├── README.md │ ├── angular-mocks.js │ ├── bower.json │ ├── ngAnimateMock.js │ ├── ngMock.js │ ├── ngMockE2E.js │ └── package.json ├── angular-module │ └── checklist-model.js ├── angular-resource │ ├── .bower.json │ ├── README.md │ ├── angular-resource.js │ ├── angular-resource.min.js │ ├── angular-resource.min.js.map │ ├── bower.json │ ├── index.js │ └── package.json ├── angular-route │ ├── .bower.json │ ├── README.md │ ├── angular-route.js │ ├── angular-route.min.js │ ├── angular-route.min.js.map │ ├── bower.json │ ├── index.js │ └── package.json ├── angular │ ├── .bower.json │ ├── README.md │ ├── angular-csp.css │ ├── angular.js │ ├── angular.min.js │ ├── angular.min.js.gzip │ ├── angular.min.js.map │ ├── bower.json │ ├── index.js │ └── package.json ├── bootstrap │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.flatly.min.css │ │ └── bootstrap.min.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── npm.js └── semantic-ui │ └── semantic.min.css ├── login.html └── partials ├── authority ├── detail.html ├── list.html └── new.html ├── backend-conf ├── detail.html ├── list.html └── new.html ├── role ├── detail.html ├── list.html └── new.html └── users ├── detail.html ├── list.html └── new.html /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | .svn 3 | .DS_Store 4 | dist 5 | release 6 | conf/aproxy_dev.toml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aproxy 2 | 3 | `aproxy` is a reverse proxy that includes authentication. It is designed to protect the resources that you want to expose, but only allow some one has you permission to access. 4 | 5 | ## Screenshot 6 | 7 | **Backend config**: 8 | 9 | ![](doc/img/backend.png) 10 | 11 | **Role List**: 12 | 13 | ![](doc/img/role.png) 14 | 15 | **Authority config**: 16 | 17 | ![](doc/img/authority.png) 18 | 19 | 20 | ## Install 21 | 22 | ### Install from source 23 | 24 | ``` 25 | cd $GOPATH/src 26 | git clone https://github.com/shunfei/aproxy.git 27 | cd aproxy 28 | sh ./install.sh 29 | ``` 30 | 31 | ### Install from tarball 32 | 33 | Go to [releases](https://github.com/shunfei/aproxy/releases) page download the tar file. 34 | 35 | ``` 36 | tar xzvf aproxy-v0.1-xxxx-xxx-xx.tar.gz 37 | cd aproxy-v0.1-xxxx-xxx-xx 38 | cp conf/aproxy.toml.example conf/aproxy.toml 39 | ``` 40 | 41 | ## Run 42 | 43 | Before running, your need set up [MongoDB](http://docs.mongodb.org/manual/installation/) and [Redis](http://redis.io/download#installation) (MongoDB for config storage, Redis for session storage), 44 | and change the config in `conf/aproxy.toml`. 45 | 46 | ``` 47 | ./bin/aproxy -c conf/aproxy.toml 48 | ``` 49 | 50 | By now there is no users in the database, so let me add a user: 51 | 52 | ``` 53 | ./bin/adduser -c conf/aproxy.toml -action adduser -email yourname@gmail.com -pwd passwordxxx 54 | ``` 55 | 56 | And the user added above do not have admin permission, so let me set it to admin. 57 | 58 | ``` 59 | ./bin/adduser -c conf/aproxy.toml -action setadmin -email yourname@gmail.com -adminlevel 99 60 | ``` 61 | 62 | And now you can visit `http://127.0.0.1:8098/-_-aproxy-_-/` and config your aproxy. 63 | 64 | ## Config 65 | 66 | `conf/aproxy.toml` 67 | 68 | ## Nginx Config Example 69 | 70 | Assuming that the resources required authorized all are the domain of `pri.domain.com`'s subdomain, 71 | Aproxy nginx server configuration should look like: 72 | 73 | ``` 74 | server { 75 | listen 80; 76 | server_name pri.domain.com *.pri.domain.com; 77 | 78 | location / { 79 | proxy_redirect off; 80 | proxy_set_header Host $host; 81 | proxy_set_header X-Real-IP $remote_addr; 82 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 83 | proxy_set_header X-Forwarded-Proto $scheme; 84 | # pass to aproxy 85 | proxy_pass http://127.0.0.1:8098; 86 | } 87 | 88 | } 89 | ``` 90 | 91 | And then set the WildCard DNS Record `*.pri.domain.com` to this nginx server. 92 | 93 | Assume that we have the following domain: 94 | 95 | - pri.domain.com 96 | - hadoop.pri.domain.com 97 | - druid.pri.domain.com 98 | - aerospike.pri.domain.com 99 | 100 | Then we can set the login domain to `pri.domain.com`, to ensure that the sub-domain of `pri.domain.com` ( for example `hadoop.pri.domain.com`) can get the session cookies after login. 101 | So we change `conf/aproxy.toml` to set the domain: 102 | 103 | ``` 104 | loginHost = "http://pri.domain.com" 105 | [session] 106 | domain = "pri.domain.com" 107 | ``` 108 | 109 | ## Integration with your company's account system 110 | 111 | Aproxy's authority is base on email, so if your company's account system has email field, can be integration. 112 | To integration with aproxy, just need implement the interface of `aproxy/module/auth/UserStorager`. 113 | 114 | ``` 115 | type UserStorager interface { 116 | Login(email, pwd string) (*User, error) 117 | GetByEmail(email string) (*User, error) 118 | GetAll() ([]User, error) 119 | // add new user. 120 | // user.Pwd field has encrypted. 121 | Insert(user User) error 122 | Update(id string, user User) error 123 | } 124 | ``` 125 | 126 | If you don't need manage the user in aproxy, you can just implement the `Login(email, pwd string) (*User, error)` func. 127 | 128 | After implement the `aproxy/module/auth/UserStorager` interface, we need change the code in `aproxy/bin/main.go`: 129 | 130 | ``` 131 | //file: aproxy/bin/main.go 132 | 133 | delete this line: 134 | //auth.SetUserStorageToMongo() 135 | 136 | add this code, to register your own UserStorager to aproxy 137 | auth.SetUserStorage(&yourUserStorage{}) 138 | ``` 139 | -------------------------------------------------------------------------------- /bin/adduser.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "strings" 8 | "time" 9 | 10 | "aproxy/conf" 11 | "aproxy/module/auth" 12 | "aproxy/module/db" 13 | ) 14 | 15 | var ( 16 | inited = false 17 | 18 | confFile = flag.String("c", "aproxy.toml", "aproxy config file path") 19 | 20 | action = flag.String("action", "", "action to do: adduser, setadmin") 21 | email = flag.String("email", "", "email address") 22 | pwd = flag.String("pwd", "", "password") 23 | adminLevel = flag.Int("adminlevel", 0, "50:System Administrator, 99:Super Administrator") 24 | ) 25 | 26 | func main() { 27 | flag.Parse() 28 | 29 | semail := strings.TrimSpace(*email) 30 | semail = strings.ToLower(semail) 31 | spwd := strings.TrimSpace(*pwd) 32 | 33 | var err error 34 | saction := *action 35 | switch saction { 36 | case "adduser": 37 | initMongo() 38 | auth.SetUserStorageToMongo() 39 | err = addUser(semail, spwd) 40 | if err == nil { 41 | fmt.Println("success add user:", semail) 42 | } 43 | case "setadmin": 44 | initMongo() 45 | err = setAdmin(semail, *adminLevel) 46 | if err == nil { 47 | fmt.Printf("success set %s to admin\n", semail) 48 | } 49 | default: 50 | if saction == "" { 51 | err = fmt.Errorf("please enter action") 52 | } else { 53 | err = fmt.Errorf("wrong action [%s]", saction) 54 | } 55 | } 56 | if err != nil { 57 | log.Fatalln("add user error: ", err.Error()) 58 | } 59 | } 60 | 61 | func initMongo() { 62 | if inited { 63 | return 64 | } 65 | err := conf.LoadAproxyConfig(*confFile) 66 | if err != nil { 67 | log.Fatalln(err) 68 | } 69 | config := conf.Config() 70 | mgoConf := config.Db.Mongo 71 | err = db.InitMongoDB(mgoConf.Servers, mgoConf.Db) 72 | if err != nil { 73 | log.Fatalln("Can not set to MongoDB backend config storage.", mgoConf.Servers) 74 | } 75 | inited = true 76 | } 77 | 78 | func addUser(email, pwd string) error { 79 | if email == "" || pwd == "" { 80 | return fmt.Errorf("please enter email and pwd") 81 | } 82 | user := auth.User{} 83 | user.Email = email 84 | user.Name = email 85 | user.Pwd = pwd 86 | user.CreatedTime = time.Now() 87 | user.UpdatedTime = user.CreatedTime 88 | err := auth.InsertUser(user) 89 | return err 90 | } 91 | 92 | func setAdmin(email string, level int) error { 93 | if level != 50 && level != 99 { 94 | return fmt.Errorf("adminlevel must be 50 or 99") 95 | } 96 | authority, err := auth.GetAuthorityByEmail(email) 97 | if err != nil { 98 | return fmt.Errorf("query Authority for %s got error: %s", 99 | email, err.Error()) 100 | } 101 | if authority != nil { 102 | authority.AdminLevel = level 103 | err = auth.UpdateAuthority(authority.Id, authority) 104 | } else { 105 | authority = &auth.Authority{} 106 | authority.Email = email 107 | authority.AdminLevel = level 108 | err = auth.InsertAuthority(authority) 109 | } 110 | return err 111 | } 112 | -------------------------------------------------------------------------------- /bin/aproxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | "os" 8 | "path/filepath" 9 | 10 | "aproxy/conf" 11 | "aproxy/lib/auditlog" 12 | "aproxy/lib/rfweb/session" 13 | "aproxy/loginservices/github" 14 | "aproxy/module/auth" 15 | "aproxy/module/auth/login" 16 | bkconf "aproxy/module/backend_conf" 17 | "aproxy/module/db" 18 | "aproxy/module/oauth" 19 | "aproxy/module/proxy" 20 | "aproxy/module/setting" 21 | ) 22 | 23 | var ( 24 | confFile = flag.String("c", "aproxy.toml", "aproxy config file path") 25 | ) 26 | 27 | func main() { 28 | log.SetFlags(log.LstdFlags | log.Lshortfile) 29 | 30 | flag.Parse() 31 | 32 | err := conf.LoadAproxyConfig(*confFile) 33 | if err != nil { 34 | log.Fatalln(err) 35 | } 36 | 37 | config := conf.Config() 38 | 39 | if !checkWebDir(config.WebDir) { 40 | os.Exit(1) 41 | return 42 | } 43 | 44 | mgoConf := config.Db.Mongo 45 | err = db.InitMongoDB(mgoConf.Servers, mgoConf.Db) 46 | if err != nil { 47 | log.Fatalln("Can not set to MongoDB backend config storage.", mgoConf.Servers) 48 | } 49 | // Set backend-config storage to MongoDB 50 | bkconf.SetBackendConfStorageToMongo() 51 | // Set user storage to MongoDB 52 | auth.SetUserStorageToMongo() 53 | 54 | // session 55 | ssConf := config.Session 56 | session.InitSessionServer(ssConf.Domain, ssConf.Cookie, ssConf.Expiration) 57 | err = session.SetSessionStoragerToRedis(ssConf.Redis.Addr, 58 | ssConf.Redis.Password, ssConf.Redis.Db) 59 | if err != nil { 60 | log.Fatalln("SetSessionStoragerToRedis faild:", err) 61 | } 62 | 63 | // login 64 | login.InitLoginServer(config.LoginHost, config.AproxyUrlPrefix) 65 | 66 | // setting manager 67 | setting.InitSettingServer(config.WebDir, config.AproxyUrlPrefix) 68 | 69 | //oauth 70 | initOauth(config) 71 | 72 | // log 73 | err = auditlog.Init(config.AuditLogPath) 74 | if err != nil { 75 | log.Fatalln("Init audit log fatal.", config.AuditLogPath, err) 76 | } 77 | 78 | lhost := config.Listen 79 | mux := http.NewServeMux() 80 | // setting 81 | setPre := setting.AproxyUrlPrefix 82 | apiApp := setting.NewApiApp() 83 | mux.HandleFunc(apiApp.UrlPrefix, apiApp.ServeHTTP) 84 | mux.HandleFunc(setPre, setting.StaticServer) 85 | // proxy 86 | mux.HandleFunc("/", proxy.Proxy) 87 | s := &http.Server{ 88 | Addr: lhost, 89 | Handler: mux, 90 | } 91 | log.Println("Starting aproxy on " + lhost) 92 | err = s.ListenAndServe() 93 | if err != nil { 94 | log.Fatal(err) 95 | } 96 | } 97 | 98 | func checkWebDir(webDir string) bool { 99 | absPath, _ := filepath.Abs(webDir) 100 | _, err := os.Stat(absPath) 101 | if err == nil { 102 | return true 103 | } 104 | if os.IsNotExist(err) { 105 | log.Println("webdir is not exist:", absPath) 106 | log.Println("please change the webdir in your aproxy config file.") 107 | return false 108 | } 109 | return true 110 | } 111 | 112 | func initOauth(config *conf.AproxyConfig) { 113 | oauthConfig := config.Oauth 114 | if oauthConfig.Open { 115 | if oauthConfig.Github.Open { 116 | github.InitGithubOauther(setting.AproxyUrlPrefix, config.LoginHost, 117 | oauthConfig.Github.ClientID, oauthConfig.Github.ClientSecret) 118 | o := github.GithubOauther{} 119 | oauth.Register(o) 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /conf/aproxy.toml: -------------------------------------------------------------------------------- 1 | ########### 2 | # title = "aProxy conf" 3 | ########### 4 | 5 | listen = ":8098" 6 | 7 | ### 8 | # aproxy's admin setting web dir 9 | webdir = "/path/to/aproxy/web/static/" 10 | 11 | ### 12 | # if loginHost is blank, will use current url hots. 13 | # or set to like "http://aproxy.loc" 14 | loginHost = "" 15 | 16 | ### 17 | # aproxy admin url prefix, 18 | # default is "/-_-aproxy-_-/", 19 | # need end with "/" 20 | aproxyUrlPrefix = "/-_-aproxy-_-/" 21 | 22 | auditLogPath = "/data/logs/aproxy-audit.log" 23 | 24 | [session] 25 | ### 26 | # session cookie domain, 27 | # e.g. abc.com 28 | domain = "" 29 | 30 | ### 31 | # session cookie name 32 | cookie = "_aproxySID" 33 | 34 | ### 35 | # session Expiration, 36 | # default is 604800 (7 days) 37 | expiration = 604800 38 | 39 | ### 40 | # session storage 41 | [session.redis] 42 | addr = "127.0.0.1:6379" 43 | password = "" 44 | db = 0 45 | 46 | [db] 47 | [db.mongo] 48 | servers = ["127.0.0.1:27017"] 49 | db = "aproxy" 50 | 51 | [backend] 52 | 53 | [oauth] 54 | # open oauth login 55 | open = true 56 | # github's oauth config 57 | [oauth.github] 58 | # open github's oauth login 59 | open = true 60 | clientID = "xxx" 61 | clientSecret = "xxxxxx" 62 | -------------------------------------------------------------------------------- /conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/BurntSushi/toml" 7 | ) 8 | 9 | type AproxyConfig struct { 10 | Listen string 11 | WebDir string 12 | LoginHost string 13 | AproxyUrlPrefix string 14 | AuditLogPath string 15 | Session struct { 16 | Cookie string 17 | Domain string 18 | Expiration int64 19 | Redis struct { 20 | Addr string 21 | Password string 22 | Db int 23 | } 24 | } 25 | Db struct { 26 | Mongo struct { 27 | Servers []string 28 | Db string 29 | } 30 | } 31 | Oauth struct { 32 | Open bool 33 | Github struct { 34 | Open bool 35 | ClientID string 36 | ClientSecret string 37 | } 38 | } 39 | } 40 | 41 | var aproxyConfig AproxyConfig 42 | 43 | func LoadAproxyConfig(tomlFile string) error { 44 | if _, err := toml.DecodeFile(tomlFile, &aproxyConfig); err != nil { 45 | return fmt.Errorf("Load config file [%s] faild: %s", 46 | tomlFile, err) 47 | } 48 | return nil 49 | } 50 | 51 | func Config() *AproxyConfig { 52 | return &aproxyConfig 53 | } 54 | -------------------------------------------------------------------------------- /doc/img/authority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shunfei/aproxy/6f66a61dbfef4dfd67e93ba18eca8a2a214322ae/doc/img/authority.png -------------------------------------------------------------------------------- /doc/img/backend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shunfei/aproxy/6f66a61dbfef4dfd67e93ba18eca8a2a214322ae/doc/img/backend.png -------------------------------------------------------------------------------- /doc/img/role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shunfei/aproxy/6f66a61dbfef4dfd67e93ba18eca8a2a214322ae/doc/img/role.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module aproxy 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/BurntSushi/toml v0.3.1 7 | github.com/google/go-github v17.0.0+incompatible 8 | github.com/google/go-querystring v1.1.0 // indirect 9 | github.com/mailgun/oxy v0.0.0-20181019102601-ac21a760928b 10 | github.com/mailgun/timetools v0.0.0-20170619190023-f3a7b8ffff47 // indirect 11 | github.com/onsi/ginkgo v1.14.2 // indirect 12 | github.com/onsi/gomega v1.10.4 // indirect 13 | github.com/smartystreets/goconvey v1.6.4 14 | github.com/vulcand/oxy v1.1.0 // indirect 15 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 16 | gopkg.in/bluesuncorp/assert.v1 v1.2.1 17 | gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce 18 | gopkg.in/redis.v5 v5.2.9 19 | ) 20 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | INSTALL_PREFIX="/usr/local" 4 | APROXY_VER="0.1" 5 | APROXY_BIN_GO="./bin/aproxy.go" 6 | 7 | if [ ! -f "$APROXY_BIN_GO" ]; then 8 | echo "please enter aproxy root dir to execute this script." 9 | exit 1 10 | fi 11 | 12 | 13 | eval $(go env) 14 | 15 | GIT_SHA=`git rev-parse --short HEAD || echo "GitNotFound"` 16 | 17 | val=$(go version) 18 | gover=$(echo $val | awk -F ' ' '{print $3}') 19 | 20 | outdir="aproxy-v$APROXY_VER-$gover-git$GIT_SHA" 21 | 22 | echo "build file to ./dist/$outdir/" 23 | 24 | rm -rf ./dist/$outdir/* 25 | mkdir -p ./dist/$outdir/bin 26 | mkdir -p ./dist/$outdir/conf 27 | 28 | go build -o ./dist/$outdir/bin/aproxy ./bin/aproxy.go 29 | go build -o ./dist/$outdir/bin/adduser ./bin/adduser.go 30 | 31 | yes|cp -rf ./web ./dist/$outdir/ 32 | yes|cp -f ./conf/aproxy.toml ./dist/$outdir/conf/aproxy.toml.example 33 | 34 | echo "install to $INSTALL_PREFIX/aproxy" 35 | 36 | mkdir -p $INSTALL_PREFIX/aproxy 37 | 38 | yes|cp -rf ./dist/$outdir/* $INSTALL_PREFIX/aproxy/ 39 | 40 | echo "build and install done." 41 | -------------------------------------------------------------------------------- /lib/auditlog/log.go: -------------------------------------------------------------------------------- 1 | // 2 | // @Author: QLeelulu 3 | // @Date: 2021-11-25 13:00:44 4 | // @LastEditors: QLeelulu 5 | // @LastEditTime: 2021-11-25 13:00:44 6 | // @FilePath: /aproxy/lib/auditlog/log.go 7 | // @Description: 8 | 9 | package auditlog 10 | 11 | import ( 12 | "fmt" 13 | "log" 14 | "os" 15 | 16 | "aproxy/module/auth" 17 | ) 18 | 19 | var logger *log.Logger 20 | 21 | // Init 初始化 22 | func Init(logPath string) error { 23 | f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) 24 | if err != nil { 25 | return fmt.Errorf("file open error : %v", err) 26 | } 27 | logger = log.New(f, "", log.Ldate|log.Ltime) 28 | return nil 29 | } 30 | 31 | // AccessLog 打印访问审计日志 32 | func AccessLog(u *auth.User, resources string) error { 33 | logger.Printf("%s[%s] %s", u.Name, u.Email, resources) 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /lib/crypto/AUTHORS: -------------------------------------------------------------------------------- 1 | # This source code refers to The Go Authors for copyright purposes. 2 | # The master list of authors is in the main Go distribution, 3 | # visible at http://tip.golang.org/AUTHORS. 4 | -------------------------------------------------------------------------------- /lib/crypto/bcrypt/base64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package bcrypt 6 | 7 | import "encoding/base64" 8 | 9 | const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 10 | 11 | var bcEncoding = base64.NewEncoding(alphabet) 12 | 13 | func base64Encode(src []byte) []byte { 14 | n := bcEncoding.EncodedLen(len(src)) 15 | dst := make([]byte, n) 16 | bcEncoding.Encode(dst, src) 17 | for dst[n-1] == '=' { 18 | n-- 19 | } 20 | return dst[:n] 21 | } 22 | 23 | func base64Decode(src []byte) ([]byte, error) { 24 | numOfEquals := 4 - (len(src) % 4) 25 | for i := 0; i < numOfEquals; i++ { 26 | src = append(src, '=') 27 | } 28 | 29 | dst := make([]byte, bcEncoding.DecodedLen(len(src))) 30 | n, err := bcEncoding.Decode(dst, src) 31 | if err != nil { 32 | return nil, err 33 | } 34 | return dst[:n], nil 35 | } 36 | -------------------------------------------------------------------------------- /lib/crypto/bcrypt/bcrypt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing 6 | // algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf 7 | package bcrypt 8 | 9 | // The code is a port of Provos and Mazières's C implementation. 10 | import ( 11 | "aproxy/lib/crypto/blowfish" 12 | "crypto/rand" 13 | "crypto/subtle" 14 | "errors" 15 | "fmt" 16 | "io" 17 | "strconv" 18 | ) 19 | 20 | const ( 21 | MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword 22 | MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword 23 | DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword 24 | ) 25 | 26 | // The error returned from CompareHashAndPassword when a password and hash do 27 | // not match. 28 | var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password") 29 | 30 | // The error returned from CompareHashAndPassword when a hash is too short to 31 | // be a bcrypt hash. 32 | var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password") 33 | 34 | // The error returned from CompareHashAndPassword when a hash was created with 35 | // a bcrypt algorithm newer than this implementation. 36 | type HashVersionTooNewError byte 37 | 38 | func (hv HashVersionTooNewError) Error() string { 39 | return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion) 40 | } 41 | 42 | // The error returned from CompareHashAndPassword when a hash starts with something other than '$' 43 | type InvalidHashPrefixError byte 44 | 45 | func (ih InvalidHashPrefixError) Error() string { 46 | return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih)) 47 | } 48 | 49 | type InvalidCostError int 50 | 51 | func (ic InvalidCostError) Error() string { 52 | return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) 53 | } 54 | 55 | const ( 56 | majorVersion = '2' 57 | minorVersion = 'a' 58 | maxSaltSize = 16 59 | maxCryptedHashSize = 23 60 | encodedSaltSize = 22 61 | encodedHashSize = 31 62 | minHashSize = 59 63 | ) 64 | 65 | // magicCipherData is an IV for the 64 Blowfish encryption calls in 66 | // bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes. 67 | var magicCipherData = []byte{ 68 | 0x4f, 0x72, 0x70, 0x68, 69 | 0x65, 0x61, 0x6e, 0x42, 70 | 0x65, 0x68, 0x6f, 0x6c, 71 | 0x64, 0x65, 0x72, 0x53, 72 | 0x63, 0x72, 0x79, 0x44, 73 | 0x6f, 0x75, 0x62, 0x74, 74 | } 75 | 76 | type hashed struct { 77 | hash []byte 78 | salt []byte 79 | cost int // allowed range is MinCost to MaxCost 80 | major byte 81 | minor byte 82 | } 83 | 84 | // GenerateFromPassword returns the bcrypt hash of the password at the given 85 | // cost. If the cost given is less than MinCost, the cost will be set to 86 | // DefaultCost, instead. Use CompareHashAndPassword, as defined in this package, 87 | // to compare the returned hashed password with its cleartext version. 88 | func GenerateFromPassword(password []byte, cost int) ([]byte, error) { 89 | p, err := newFromPassword(password, cost) 90 | if err != nil { 91 | return nil, err 92 | } 93 | return p.Hash(), nil 94 | } 95 | 96 | // CompareHashAndPassword compares a bcrypt hashed password with its possible 97 | // plaintext equivalent. Returns nil on success, or an error on failure. 98 | func CompareHashAndPassword(hashedPassword, password []byte) error { 99 | p, err := newFromHash(hashedPassword) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | otherHash, err := bcrypt(password, p.cost, p.salt) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} 110 | if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { 111 | return nil 112 | } 113 | 114 | return ErrMismatchedHashAndPassword 115 | } 116 | 117 | // Cost returns the hashing cost used to create the given hashed 118 | // password. When, in the future, the hashing cost of a password system needs 119 | // to be increased in order to adjust for greater computational power, this 120 | // function allows one to establish which passwords need to be updated. 121 | func Cost(hashedPassword []byte) (int, error) { 122 | p, err := newFromHash(hashedPassword) 123 | if err != nil { 124 | return 0, err 125 | } 126 | return p.cost, nil 127 | } 128 | 129 | func newFromPassword(password []byte, cost int) (*hashed, error) { 130 | if cost < MinCost { 131 | cost = DefaultCost 132 | } 133 | p := new(hashed) 134 | p.major = majorVersion 135 | p.minor = minorVersion 136 | 137 | err := checkCost(cost) 138 | if err != nil { 139 | return nil, err 140 | } 141 | p.cost = cost 142 | 143 | unencodedSalt := make([]byte, maxSaltSize) 144 | _, err = io.ReadFull(rand.Reader, unencodedSalt) 145 | if err != nil { 146 | return nil, err 147 | } 148 | 149 | p.salt = base64Encode(unencodedSalt) 150 | hash, err := bcrypt(password, p.cost, p.salt) 151 | if err != nil { 152 | return nil, err 153 | } 154 | p.hash = hash 155 | return p, err 156 | } 157 | 158 | func newFromHash(hashedSecret []byte) (*hashed, error) { 159 | if len(hashedSecret) < minHashSize { 160 | return nil, ErrHashTooShort 161 | } 162 | p := new(hashed) 163 | n, err := p.decodeVersion(hashedSecret) 164 | if err != nil { 165 | return nil, err 166 | } 167 | hashedSecret = hashedSecret[n:] 168 | n, err = p.decodeCost(hashedSecret) 169 | if err != nil { 170 | return nil, err 171 | } 172 | hashedSecret = hashedSecret[n:] 173 | 174 | // The "+2" is here because we'll have to append at most 2 '=' to the salt 175 | // when base64 decoding it in expensiveBlowfishSetup(). 176 | p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) 177 | copy(p.salt, hashedSecret[:encodedSaltSize]) 178 | 179 | hashedSecret = hashedSecret[encodedSaltSize:] 180 | p.hash = make([]byte, len(hashedSecret)) 181 | copy(p.hash, hashedSecret) 182 | 183 | return p, nil 184 | } 185 | 186 | func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { 187 | cipherData := make([]byte, len(magicCipherData)) 188 | copy(cipherData, magicCipherData) 189 | 190 | c, err := expensiveBlowfishSetup(password, uint32(cost), salt) 191 | if err != nil { 192 | return nil, err 193 | } 194 | 195 | for i := 0; i < 24; i += 8 { 196 | for j := 0; j < 64; j++ { 197 | c.Encrypt(cipherData[i:i+8], cipherData[i:i+8]) 198 | } 199 | } 200 | 201 | // Bug compatibility with C bcrypt implementations. We only encode 23 of 202 | // the 24 bytes encrypted. 203 | hsh := base64Encode(cipherData[:maxCryptedHashSize]) 204 | return hsh, nil 205 | } 206 | 207 | func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { 208 | 209 | csalt, err := base64Decode(salt) 210 | if err != nil { 211 | return nil, err 212 | } 213 | 214 | // Bug compatibility with C bcrypt implementations. They use the trailing 215 | // NULL in the key string during expansion. 216 | ckey := append(key, 0) 217 | 218 | c, err := blowfish.NewSaltedCipher(ckey, csalt) 219 | if err != nil { 220 | return nil, err 221 | } 222 | 223 | var i, rounds uint64 224 | rounds = 1 << cost 225 | for i = 0; i < rounds; i++ { 226 | blowfish.ExpandKey(ckey, c) 227 | blowfish.ExpandKey(csalt, c) 228 | } 229 | 230 | return c, nil 231 | } 232 | 233 | func (p *hashed) Hash() []byte { 234 | arr := make([]byte, 60) 235 | arr[0] = '$' 236 | arr[1] = p.major 237 | n := 2 238 | if p.minor != 0 { 239 | arr[2] = p.minor 240 | n = 3 241 | } 242 | arr[n] = '$' 243 | n += 1 244 | copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) 245 | n += 2 246 | arr[n] = '$' 247 | n += 1 248 | copy(arr[n:], p.salt) 249 | n += encodedSaltSize 250 | copy(arr[n:], p.hash) 251 | n += encodedHashSize 252 | return arr[:n] 253 | } 254 | 255 | func (p *hashed) decodeVersion(sbytes []byte) (int, error) { 256 | if sbytes[0] != '$' { 257 | return -1, InvalidHashPrefixError(sbytes[0]) 258 | } 259 | if sbytes[1] > majorVersion { 260 | return -1, HashVersionTooNewError(sbytes[1]) 261 | } 262 | p.major = sbytes[1] 263 | n := 3 264 | if sbytes[2] != '$' { 265 | p.minor = sbytes[2] 266 | n++ 267 | } 268 | return n, nil 269 | } 270 | 271 | // sbytes should begin where decodeVersion left off. 272 | func (p *hashed) decodeCost(sbytes []byte) (int, error) { 273 | cost, err := strconv.Atoi(string(sbytes[0:2])) 274 | if err != nil { 275 | return -1, err 276 | } 277 | err = checkCost(cost) 278 | if err != nil { 279 | return -1, err 280 | } 281 | p.cost = cost 282 | return 3, nil 283 | } 284 | 285 | func (p *hashed) String() string { 286 | return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) 287 | } 288 | 289 | func checkCost(cost int) error { 290 | if cost < MinCost || cost > MaxCost { 291 | return InvalidCostError(cost) 292 | } 293 | return nil 294 | } 295 | -------------------------------------------------------------------------------- /lib/crypto/bcrypt/bcrypt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package bcrypt 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "testing" 11 | ) 12 | 13 | func TestBcryptingIsEasy(t *testing.T) { 14 | pass := []byte("mypassword") 15 | hp, err := GenerateFromPassword(pass, 0) 16 | if err != nil { 17 | t.Fatalf("GenerateFromPassword error: %s", err) 18 | } 19 | 20 | if CompareHashAndPassword(hp, pass) != nil { 21 | t.Errorf("%v should hash %s correctly", hp, pass) 22 | } 23 | 24 | notPass := "notthepass" 25 | err = CompareHashAndPassword(hp, []byte(notPass)) 26 | if err != ErrMismatchedHashAndPassword { 27 | t.Errorf("%v and %s should be mismatched", hp, notPass) 28 | } 29 | } 30 | 31 | func TestBcryptingIsCorrect(t *testing.T) { 32 | pass := []byte("allmine") 33 | salt := []byte("XajjQvNhvvRt5GSeFk1xFe") 34 | expectedHash := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga") 35 | 36 | hash, err := bcrypt(pass, 10, salt) 37 | if err != nil { 38 | t.Fatalf("bcrypt blew up: %v", err) 39 | } 40 | if !bytes.HasSuffix(expectedHash, hash) { 41 | t.Errorf("%v should be the suffix of %v", hash, expectedHash) 42 | } 43 | 44 | h, err := newFromHash(expectedHash) 45 | if err != nil { 46 | t.Errorf("Unable to parse %s: %v", string(expectedHash), err) 47 | } 48 | 49 | // This is not the safe way to compare these hashes. We do this only for 50 | // testing clarity. Use bcrypt.CompareHashAndPassword() 51 | if err == nil && !bytes.Equal(expectedHash, h.Hash()) { 52 | t.Errorf("Parsed hash %v should equal %v", h.Hash(), expectedHash) 53 | } 54 | } 55 | 56 | func TestVeryShortPasswords(t *testing.T) { 57 | key := []byte("k") 58 | salt := []byte("XajjQvNhvvRt5GSeFk1xFe") 59 | _, err := bcrypt(key, 10, salt) 60 | if err != nil { 61 | t.Errorf("One byte key resulted in error: %s", err) 62 | } 63 | } 64 | 65 | func TestTooLongPasswordsWork(t *testing.T) { 66 | salt := []byte("XajjQvNhvvRt5GSeFk1xFe") 67 | // One byte over the usual 56 byte limit that blowfish has 68 | tooLongPass := []byte("012345678901234567890123456789012345678901234567890123456") 69 | tooLongExpected := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C") 70 | hash, err := bcrypt(tooLongPass, 10, salt) 71 | if err != nil { 72 | t.Fatalf("bcrypt blew up on long password: %v", err) 73 | } 74 | if !bytes.HasSuffix(tooLongExpected, hash) { 75 | t.Errorf("%v should be the suffix of %v", hash, tooLongExpected) 76 | } 77 | } 78 | 79 | type InvalidHashTest struct { 80 | err error 81 | hash []byte 82 | } 83 | 84 | var invalidTests = []InvalidHashTest{ 85 | {ErrHashTooShort, []byte("$2a$10$fooo")}, 86 | {ErrHashTooShort, []byte("$2a")}, 87 | {HashVersionTooNewError('3'), []byte("$3a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")}, 88 | {InvalidHashPrefixError('%'), []byte("%2a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")}, 89 | {InvalidCostError(32), []byte("$2a$32$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")}, 90 | } 91 | 92 | func TestInvalidHashErrors(t *testing.T) { 93 | check := func(name string, expected, err error) { 94 | if err == nil { 95 | t.Errorf("%s: Should have returned an error", name) 96 | } 97 | if err != nil && err != expected { 98 | t.Errorf("%s gave err %v but should have given %v", name, err, expected) 99 | } 100 | } 101 | for _, iht := range invalidTests { 102 | _, err := newFromHash(iht.hash) 103 | check("newFromHash", iht.err, err) 104 | err = CompareHashAndPassword(iht.hash, []byte("anything")) 105 | check("CompareHashAndPassword", iht.err, err) 106 | } 107 | } 108 | 109 | func TestUnpaddedBase64Encoding(t *testing.T) { 110 | original := []byte{101, 201, 101, 75, 19, 227, 199, 20, 239, 236, 133, 32, 30, 109, 243, 30} 111 | encodedOriginal := []byte("XajjQvNhvvRt5GSeFk1xFe") 112 | 113 | encoded := base64Encode(original) 114 | 115 | if !bytes.Equal(encodedOriginal, encoded) { 116 | t.Errorf("Encoded %v should have equaled %v", encoded, encodedOriginal) 117 | } 118 | 119 | decoded, err := base64Decode(encodedOriginal) 120 | if err != nil { 121 | t.Fatalf("base64Decode blew up: %s", err) 122 | } 123 | 124 | if !bytes.Equal(decoded, original) { 125 | t.Errorf("Decoded %v should have equaled %v", decoded, original) 126 | } 127 | } 128 | 129 | func TestCost(t *testing.T) { 130 | suffix := "XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C" 131 | for _, vers := range []string{"2a", "2"} { 132 | for _, cost := range []int{4, 10} { 133 | s := fmt.Sprintf("$%s$%02d$%s", vers, cost, suffix) 134 | h := []byte(s) 135 | actual, err := Cost(h) 136 | if err != nil { 137 | t.Errorf("Cost, error: %s", err) 138 | continue 139 | } 140 | if actual != cost { 141 | t.Errorf("Cost, expected: %d, actual: %d", cost, actual) 142 | } 143 | } 144 | } 145 | _, err := Cost([]byte("$a$a$" + suffix)) 146 | if err == nil { 147 | t.Errorf("Cost, malformed but no error returned") 148 | } 149 | } 150 | 151 | func TestCostValidationInHash(t *testing.T) { 152 | if testing.Short() { 153 | return 154 | } 155 | 156 | pass := []byte("mypassword") 157 | 158 | for c := 0; c < MinCost; c++ { 159 | p, _ := newFromPassword(pass, c) 160 | if p.cost != DefaultCost { 161 | t.Errorf("newFromPassword should default costs below %d to %d, but was %d", MinCost, DefaultCost, p.cost) 162 | } 163 | } 164 | 165 | p, _ := newFromPassword(pass, 14) 166 | if p.cost != 14 { 167 | t.Errorf("newFromPassword should default cost to 14, but was %d", p.cost) 168 | } 169 | 170 | hp, _ := newFromHash(p.Hash()) 171 | if p.cost != hp.cost { 172 | t.Errorf("newFromHash should maintain the cost at %d, but was %d", p.cost, hp.cost) 173 | } 174 | 175 | _, err := newFromPassword(pass, 32) 176 | if err == nil { 177 | t.Fatalf("newFromPassword: should return a cost error") 178 | } 179 | if err != InvalidCostError(32) { 180 | t.Errorf("newFromPassword: should return cost error, got %#v", err) 181 | } 182 | } 183 | 184 | func TestCostReturnsWithLeadingZeroes(t *testing.T) { 185 | hp, _ := newFromPassword([]byte("abcdefgh"), 7) 186 | cost := hp.Hash()[4:7] 187 | expected := []byte("07$") 188 | 189 | if !bytes.Equal(expected, cost) { 190 | t.Errorf("single digit costs in hash should have leading zeros: was %v instead of %v", cost, expected) 191 | } 192 | } 193 | 194 | func TestMinorNotRequired(t *testing.T) { 195 | noMinorHash := []byte("$2$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga") 196 | h, err := newFromHash(noMinorHash) 197 | if err != nil { 198 | t.Fatalf("No minor hash blew up: %s", err) 199 | } 200 | if h.minor != 0 { 201 | t.Errorf("Should leave minor version at 0, but was %d", h.minor) 202 | } 203 | 204 | if !bytes.Equal(noMinorHash, h.Hash()) { 205 | t.Errorf("Should generate hash %v, but created %v", noMinorHash, h.Hash()) 206 | } 207 | } 208 | 209 | func BenchmarkEqual(b *testing.B) { 210 | b.StopTimer() 211 | passwd := []byte("somepasswordyoulike") 212 | hash, _ := GenerateFromPassword(passwd, 10) 213 | b.StartTimer() 214 | for i := 0; i < b.N; i++ { 215 | CompareHashAndPassword(hash, passwd) 216 | } 217 | } 218 | 219 | func BenchmarkGeneration(b *testing.B) { 220 | b.StopTimer() 221 | passwd := []byte("mylongpassword1234") 222 | b.StartTimer() 223 | for i := 0; i < b.N; i++ { 224 | GenerateFromPassword(passwd, 10) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /lib/crypto/blowfish/block.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package blowfish 6 | 7 | // getNextWord returns the next big-endian uint32 value from the byte slice 8 | // at the given position in a circular manner, updating the position. 9 | func getNextWord(b []byte, pos *int) uint32 { 10 | var w uint32 11 | j := *pos 12 | for i := 0; i < 4; i++ { 13 | w = w<<8 | uint32(b[j]) 14 | j++ 15 | if j >= len(b) { 16 | j = 0 17 | } 18 | } 19 | *pos = j 20 | return w 21 | } 22 | 23 | // ExpandKey performs a key expansion on the given *Cipher. Specifically, it 24 | // performs the Blowfish algorithm's key schedule which sets up the *Cipher's 25 | // pi and substitution tables for calls to Encrypt. This is used, primarily, 26 | // by the bcrypt package to reuse the Blowfish key schedule during its 27 | // set up. It's unlikely that you need to use this directly. 28 | func ExpandKey(key []byte, c *Cipher) { 29 | j := 0 30 | for i := 0; i < 18; i++ { 31 | // Using inlined getNextWord for performance. 32 | var d uint32 33 | for k := 0; k < 4; k++ { 34 | d = d<<8 | uint32(key[j]) 35 | j++ 36 | if j >= len(key) { 37 | j = 0 38 | } 39 | } 40 | c.p[i] ^= d 41 | } 42 | 43 | var l, r uint32 44 | for i := 0; i < 18; i += 2 { 45 | l, r = encryptBlock(l, r, c) 46 | c.p[i], c.p[i+1] = l, r 47 | } 48 | 49 | for i := 0; i < 256; i += 2 { 50 | l, r = encryptBlock(l, r, c) 51 | c.s0[i], c.s0[i+1] = l, r 52 | } 53 | for i := 0; i < 256; i += 2 { 54 | l, r = encryptBlock(l, r, c) 55 | c.s1[i], c.s1[i+1] = l, r 56 | } 57 | for i := 0; i < 256; i += 2 { 58 | l, r = encryptBlock(l, r, c) 59 | c.s2[i], c.s2[i+1] = l, r 60 | } 61 | for i := 0; i < 256; i += 2 { 62 | l, r = encryptBlock(l, r, c) 63 | c.s3[i], c.s3[i+1] = l, r 64 | } 65 | } 66 | 67 | // This is similar to ExpandKey, but folds the salt during the key 68 | // schedule. While ExpandKey is essentially expandKeyWithSalt with an all-zero 69 | // salt passed in, reusing ExpandKey turns out to be a place of inefficiency 70 | // and specializing it here is useful. 71 | func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) { 72 | j := 0 73 | for i := 0; i < 18; i++ { 74 | c.p[i] ^= getNextWord(key, &j) 75 | } 76 | 77 | j = 0 78 | var l, r uint32 79 | for i := 0; i < 18; i += 2 { 80 | l ^= getNextWord(salt, &j) 81 | r ^= getNextWord(salt, &j) 82 | l, r = encryptBlock(l, r, c) 83 | c.p[i], c.p[i+1] = l, r 84 | } 85 | 86 | for i := 0; i < 256; i += 2 { 87 | l ^= getNextWord(salt, &j) 88 | r ^= getNextWord(salt, &j) 89 | l, r = encryptBlock(l, r, c) 90 | c.s0[i], c.s0[i+1] = l, r 91 | } 92 | 93 | for i := 0; i < 256; i += 2 { 94 | l ^= getNextWord(salt, &j) 95 | r ^= getNextWord(salt, &j) 96 | l, r = encryptBlock(l, r, c) 97 | c.s1[i], c.s1[i+1] = l, r 98 | } 99 | 100 | for i := 0; i < 256; i += 2 { 101 | l ^= getNextWord(salt, &j) 102 | r ^= getNextWord(salt, &j) 103 | l, r = encryptBlock(l, r, c) 104 | c.s2[i], c.s2[i+1] = l, r 105 | } 106 | 107 | for i := 0; i < 256; i += 2 { 108 | l ^= getNextWord(salt, &j) 109 | r ^= getNextWord(salt, &j) 110 | l, r = encryptBlock(l, r, c) 111 | c.s3[i], c.s3[i+1] = l, r 112 | } 113 | } 114 | 115 | func encryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { 116 | xl, xr := l, r 117 | xl ^= c.p[0] 118 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[1] 119 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[2] 120 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[3] 121 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[4] 122 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[5] 123 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[6] 124 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[7] 125 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[8] 126 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[9] 127 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[10] 128 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[11] 129 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[12] 130 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[13] 131 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[14] 132 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[15] 133 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[16] 134 | xr ^= c.p[17] 135 | return xr, xl 136 | } 137 | 138 | func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) { 139 | xl, xr := l, r 140 | xl ^= c.p[17] 141 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[16] 142 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[15] 143 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[14] 144 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[13] 145 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[12] 146 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[11] 147 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[10] 148 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[9] 149 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[8] 150 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[7] 151 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[6] 152 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[5] 153 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[4] 154 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[3] 155 | xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[2] 156 | xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[1] 157 | xr ^= c.p[0] 158 | return xr, xl 159 | } 160 | -------------------------------------------------------------------------------- /lib/crypto/blowfish/blowfish_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package blowfish 6 | 7 | import "testing" 8 | 9 | type CryptTest struct { 10 | key []byte 11 | in []byte 12 | out []byte 13 | } 14 | 15 | // Test vector values are from http://www.schneier.com/code/vectors.txt. 16 | var encryptTests = []CryptTest{ 17 | { 18 | []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 19 | []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 20 | []byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}}, 21 | { 22 | []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 23 | []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 24 | []byte{0x51, 0x86, 0x6F, 0xD5, 0xB8, 0x5E, 0xCB, 0x8A}}, 25 | { 26 | []byte{0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 27 | []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, 28 | []byte{0x7D, 0x85, 0x6F, 0x9A, 0x61, 0x30, 0x63, 0xF2}}, 29 | { 30 | []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, 31 | []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, 32 | []byte{0x24, 0x66, 0xDD, 0x87, 0x8B, 0x96, 0x3C, 0x9D}}, 33 | 34 | { 35 | []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, 36 | []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, 37 | []byte{0x61, 0xF9, 0xC3, 0x80, 0x22, 0x81, 0xB0, 0x96}}, 38 | { 39 | []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, 40 | []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, 41 | []byte{0x7D, 0x0C, 0xC6, 0x30, 0xAF, 0xDA, 0x1E, 0xC7}}, 42 | { 43 | []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 44 | []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 45 | []byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}}, 46 | { 47 | []byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}, 48 | []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, 49 | []byte{0x0A, 0xCE, 0xAB, 0x0F, 0xC6, 0xA0, 0xA2, 0x8D}}, 50 | { 51 | []byte{0x7C, 0xA1, 0x10, 0x45, 0x4A, 0x1A, 0x6E, 0x57}, 52 | []byte{0x01, 0xA1, 0xD6, 0xD0, 0x39, 0x77, 0x67, 0x42}, 53 | []byte{0x59, 0xC6, 0x82, 0x45, 0xEB, 0x05, 0x28, 0x2B}}, 54 | { 55 | []byte{0x01, 0x31, 0xD9, 0x61, 0x9D, 0xC1, 0x37, 0x6E}, 56 | []byte{0x5C, 0xD5, 0x4C, 0xA8, 0x3D, 0xEF, 0x57, 0xDA}, 57 | []byte{0xB1, 0xB8, 0xCC, 0x0B, 0x25, 0x0F, 0x09, 0xA0}}, 58 | { 59 | []byte{0x07, 0xA1, 0x13, 0x3E, 0x4A, 0x0B, 0x26, 0x86}, 60 | []byte{0x02, 0x48, 0xD4, 0x38, 0x06, 0xF6, 0x71, 0x72}, 61 | []byte{0x17, 0x30, 0xE5, 0x77, 0x8B, 0xEA, 0x1D, 0xA4}}, 62 | { 63 | []byte{0x38, 0x49, 0x67, 0x4C, 0x26, 0x02, 0x31, 0x9E}, 64 | []byte{0x51, 0x45, 0x4B, 0x58, 0x2D, 0xDF, 0x44, 0x0A}, 65 | []byte{0xA2, 0x5E, 0x78, 0x56, 0xCF, 0x26, 0x51, 0xEB}}, 66 | { 67 | []byte{0x04, 0xB9, 0x15, 0xBA, 0x43, 0xFE, 0xB5, 0xB6}, 68 | []byte{0x42, 0xFD, 0x44, 0x30, 0x59, 0x57, 0x7F, 0xA2}, 69 | []byte{0x35, 0x38, 0x82, 0xB1, 0x09, 0xCE, 0x8F, 0x1A}}, 70 | { 71 | []byte{0x01, 0x13, 0xB9, 0x70, 0xFD, 0x34, 0xF2, 0xCE}, 72 | []byte{0x05, 0x9B, 0x5E, 0x08, 0x51, 0xCF, 0x14, 0x3A}, 73 | []byte{0x48, 0xF4, 0xD0, 0x88, 0x4C, 0x37, 0x99, 0x18}}, 74 | { 75 | []byte{0x01, 0x70, 0xF1, 0x75, 0x46, 0x8F, 0xB5, 0xE6}, 76 | []byte{0x07, 0x56, 0xD8, 0xE0, 0x77, 0x47, 0x61, 0xD2}, 77 | []byte{0x43, 0x21, 0x93, 0xB7, 0x89, 0x51, 0xFC, 0x98}}, 78 | { 79 | []byte{0x43, 0x29, 0x7F, 0xAD, 0x38, 0xE3, 0x73, 0xFE}, 80 | []byte{0x76, 0x25, 0x14, 0xB8, 0x29, 0xBF, 0x48, 0x6A}, 81 | []byte{0x13, 0xF0, 0x41, 0x54, 0xD6, 0x9D, 0x1A, 0xE5}}, 82 | { 83 | []byte{0x07, 0xA7, 0x13, 0x70, 0x45, 0xDA, 0x2A, 0x16}, 84 | []byte{0x3B, 0xDD, 0x11, 0x90, 0x49, 0x37, 0x28, 0x02}, 85 | []byte{0x2E, 0xED, 0xDA, 0x93, 0xFF, 0xD3, 0x9C, 0x79}}, 86 | { 87 | []byte{0x04, 0x68, 0x91, 0x04, 0xC2, 0xFD, 0x3B, 0x2F}, 88 | []byte{0x26, 0x95, 0x5F, 0x68, 0x35, 0xAF, 0x60, 0x9A}, 89 | []byte{0xD8, 0x87, 0xE0, 0x39, 0x3C, 0x2D, 0xA6, 0xE3}}, 90 | { 91 | []byte{0x37, 0xD0, 0x6B, 0xB5, 0x16, 0xCB, 0x75, 0x46}, 92 | []byte{0x16, 0x4D, 0x5E, 0x40, 0x4F, 0x27, 0x52, 0x32}, 93 | []byte{0x5F, 0x99, 0xD0, 0x4F, 0x5B, 0x16, 0x39, 0x69}}, 94 | { 95 | []byte{0x1F, 0x08, 0x26, 0x0D, 0x1A, 0xC2, 0x46, 0x5E}, 96 | []byte{0x6B, 0x05, 0x6E, 0x18, 0x75, 0x9F, 0x5C, 0xCA}, 97 | []byte{0x4A, 0x05, 0x7A, 0x3B, 0x24, 0xD3, 0x97, 0x7B}}, 98 | { 99 | []byte{0x58, 0x40, 0x23, 0x64, 0x1A, 0xBA, 0x61, 0x76}, 100 | []byte{0x00, 0x4B, 0xD6, 0xEF, 0x09, 0x17, 0x60, 0x62}, 101 | []byte{0x45, 0x20, 0x31, 0xC1, 0xE4, 0xFA, 0xDA, 0x8E}}, 102 | { 103 | []byte{0x02, 0x58, 0x16, 0x16, 0x46, 0x29, 0xB0, 0x07}, 104 | []byte{0x48, 0x0D, 0x39, 0x00, 0x6E, 0xE7, 0x62, 0xF2}, 105 | []byte{0x75, 0x55, 0xAE, 0x39, 0xF5, 0x9B, 0x87, 0xBD}}, 106 | { 107 | []byte{0x49, 0x79, 0x3E, 0xBC, 0x79, 0xB3, 0x25, 0x8F}, 108 | []byte{0x43, 0x75, 0x40, 0xC8, 0x69, 0x8F, 0x3C, 0xFA}, 109 | []byte{0x53, 0xC5, 0x5F, 0x9C, 0xB4, 0x9F, 0xC0, 0x19}}, 110 | { 111 | []byte{0x4F, 0xB0, 0x5E, 0x15, 0x15, 0xAB, 0x73, 0xA7}, 112 | []byte{0x07, 0x2D, 0x43, 0xA0, 0x77, 0x07, 0x52, 0x92}, 113 | []byte{0x7A, 0x8E, 0x7B, 0xFA, 0x93, 0x7E, 0x89, 0xA3}}, 114 | { 115 | []byte{0x49, 0xE9, 0x5D, 0x6D, 0x4C, 0xA2, 0x29, 0xBF}, 116 | []byte{0x02, 0xFE, 0x55, 0x77, 0x81, 0x17, 0xF1, 0x2A}, 117 | []byte{0xCF, 0x9C, 0x5D, 0x7A, 0x49, 0x86, 0xAD, 0xB5}}, 118 | { 119 | []byte{0x01, 0x83, 0x10, 0xDC, 0x40, 0x9B, 0x26, 0xD6}, 120 | []byte{0x1D, 0x9D, 0x5C, 0x50, 0x18, 0xF7, 0x28, 0xC2}, 121 | []byte{0xD1, 0xAB, 0xB2, 0x90, 0x65, 0x8B, 0xC7, 0x78}}, 122 | { 123 | []byte{0x1C, 0x58, 0x7F, 0x1C, 0x13, 0x92, 0x4F, 0xEF}, 124 | []byte{0x30, 0x55, 0x32, 0x28, 0x6D, 0x6F, 0x29, 0x5A}, 125 | []byte{0x55, 0xCB, 0x37, 0x74, 0xD1, 0x3E, 0xF2, 0x01}}, 126 | { 127 | []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 128 | []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, 129 | []byte{0xFA, 0x34, 0xEC, 0x48, 0x47, 0xB2, 0x68, 0xB2}}, 130 | { 131 | []byte{0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E}, 132 | []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, 133 | []byte{0xA7, 0x90, 0x79, 0x51, 0x08, 0xEA, 0x3C, 0xAE}}, 134 | { 135 | []byte{0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE}, 136 | []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, 137 | []byte{0xC3, 0x9E, 0x07, 0x2D, 0x9F, 0xAC, 0x63, 0x1D}}, 138 | { 139 | []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 140 | []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 141 | []byte{0x01, 0x49, 0x33, 0xE0, 0xCD, 0xAF, 0xF6, 0xE4}}, 142 | { 143 | []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 144 | []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 145 | []byte{0xF2, 0x1E, 0x9A, 0x77, 0xB7, 0x1C, 0x49, 0xBC}}, 146 | { 147 | []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, 148 | []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 149 | []byte{0x24, 0x59, 0x46, 0x88, 0x57, 0x54, 0x36, 0x9A}}, 150 | { 151 | []byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}, 152 | []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 153 | []byte{0x6B, 0x5C, 0x5A, 0x9C, 0x5D, 0x9E, 0x0A, 0x5A}}, 154 | } 155 | 156 | func TestCipherEncrypt(t *testing.T) { 157 | for i, tt := range encryptTests { 158 | c, err := NewCipher(tt.key) 159 | if err != nil { 160 | t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err) 161 | continue 162 | } 163 | ct := make([]byte, len(tt.out)) 164 | c.Encrypt(ct, tt.in) 165 | for j, v := range ct { 166 | if v != tt.out[j] { 167 | t.Errorf("Cipher.Encrypt, test vector #%d: cipher-text[%d] = %#x, expected %#x", i, j, v, tt.out[j]) 168 | break 169 | } 170 | } 171 | } 172 | } 173 | 174 | func TestCipherDecrypt(t *testing.T) { 175 | for i, tt := range encryptTests { 176 | c, err := NewCipher(tt.key) 177 | if err != nil { 178 | t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err) 179 | continue 180 | } 181 | pt := make([]byte, len(tt.in)) 182 | c.Decrypt(pt, tt.out) 183 | for j, v := range pt { 184 | if v != tt.in[j] { 185 | t.Errorf("Cipher.Decrypt, test vector #%d: plain-text[%d] = %#x, expected %#x", i, j, v, tt.in[j]) 186 | break 187 | } 188 | } 189 | } 190 | } 191 | 192 | func TestSaltedCipherKeyLength(t *testing.T) { 193 | if _, err := NewSaltedCipher(nil, []byte{'a'}); err != KeySizeError(0) { 194 | t.Errorf("NewSaltedCipher with short key, gave error %#v, expected %#v", err, KeySizeError(0)) 195 | } 196 | 197 | // A 57-byte key. One over the typical blowfish restriction. 198 | key := []byte("012345678901234567890123456789012345678901234567890123456") 199 | if _, err := NewSaltedCipher(key, []byte{'a'}); err != nil { 200 | t.Errorf("NewSaltedCipher with long key, gave error %#v", err) 201 | } 202 | } 203 | 204 | // Test vectors generated with Blowfish from OpenSSH. 205 | var saltedVectors = [][8]byte{ 206 | {0x0c, 0x82, 0x3b, 0x7b, 0x8d, 0x01, 0x4b, 0x7e}, 207 | {0xd1, 0xe1, 0x93, 0xf0, 0x70, 0xa6, 0xdb, 0x12}, 208 | {0xfc, 0x5e, 0xba, 0xde, 0xcb, 0xf8, 0x59, 0xad}, 209 | {0x8a, 0x0c, 0x76, 0xe7, 0xdd, 0x2c, 0xd3, 0xa8}, 210 | {0x2c, 0xcb, 0x7b, 0xee, 0xac, 0x7b, 0x7f, 0xf8}, 211 | {0xbb, 0xf6, 0x30, 0x6f, 0xe1, 0x5d, 0x62, 0xbf}, 212 | {0x97, 0x1e, 0xc1, 0x3d, 0x3d, 0xe0, 0x11, 0xe9}, 213 | {0x06, 0xd7, 0x4d, 0xb1, 0x80, 0xa3, 0xb1, 0x38}, 214 | {0x67, 0xa1, 0xa9, 0x75, 0x0e, 0x5b, 0xc6, 0xb4}, 215 | {0x51, 0x0f, 0x33, 0x0e, 0x4f, 0x67, 0xd2, 0x0c}, 216 | {0xf1, 0x73, 0x7e, 0xd8, 0x44, 0xea, 0xdb, 0xe5}, 217 | {0x14, 0x0e, 0x16, 0xce, 0x7f, 0x4a, 0x9c, 0x7b}, 218 | {0x4b, 0xfe, 0x43, 0xfd, 0xbf, 0x36, 0x04, 0x47}, 219 | {0xb1, 0xeb, 0x3e, 0x15, 0x36, 0xa7, 0xbb, 0xe2}, 220 | {0x6d, 0x0b, 0x41, 0xdd, 0x00, 0x98, 0x0b, 0x19}, 221 | {0xd3, 0xce, 0x45, 0xce, 0x1d, 0x56, 0xb7, 0xfc}, 222 | {0xd9, 0xf0, 0xfd, 0xda, 0xc0, 0x23, 0xb7, 0x93}, 223 | {0x4c, 0x6f, 0xa1, 0xe4, 0x0c, 0xa8, 0xca, 0x57}, 224 | {0xe6, 0x2f, 0x28, 0xa7, 0x0c, 0x94, 0x0d, 0x08}, 225 | {0x8f, 0xe3, 0xf0, 0xb6, 0x29, 0xe3, 0x44, 0x03}, 226 | {0xff, 0x98, 0xdd, 0x04, 0x45, 0xb4, 0x6d, 0x1f}, 227 | {0x9e, 0x45, 0x4d, 0x18, 0x40, 0x53, 0xdb, 0xef}, 228 | {0xb7, 0x3b, 0xef, 0x29, 0xbe, 0xa8, 0x13, 0x71}, 229 | {0x02, 0x54, 0x55, 0x41, 0x8e, 0x04, 0xfc, 0xad}, 230 | {0x6a, 0x0a, 0xee, 0x7c, 0x10, 0xd9, 0x19, 0xfe}, 231 | {0x0a, 0x22, 0xd9, 0x41, 0xcc, 0x23, 0x87, 0x13}, 232 | {0x6e, 0xff, 0x1f, 0xff, 0x36, 0x17, 0x9c, 0xbe}, 233 | {0x79, 0xad, 0xb7, 0x40, 0xf4, 0x9f, 0x51, 0xa6}, 234 | {0x97, 0x81, 0x99, 0xa4, 0xde, 0x9e, 0x9f, 0xb6}, 235 | {0x12, 0x19, 0x7a, 0x28, 0xd0, 0xdc, 0xcc, 0x92}, 236 | {0x81, 0xda, 0x60, 0x1e, 0x0e, 0xdd, 0x65, 0x56}, 237 | {0x7d, 0x76, 0x20, 0xb2, 0x73, 0xc9, 0x9e, 0xee}, 238 | } 239 | 240 | func TestSaltedCipher(t *testing.T) { 241 | var key, salt [32]byte 242 | for i := range key { 243 | key[i] = byte(i) 244 | salt[i] = byte(i + 32) 245 | } 246 | for i, v := range saltedVectors { 247 | c, err := NewSaltedCipher(key[:], salt[:i]) 248 | if err != nil { 249 | t.Fatal(err) 250 | } 251 | var buf [8]byte 252 | c.Encrypt(buf[:], buf[:]) 253 | if v != buf { 254 | t.Errorf("%d: expected %x, got %x", i, v, buf) 255 | } 256 | } 257 | } 258 | 259 | func BenchmarkExpandKeyWithSalt(b *testing.B) { 260 | key := make([]byte, 32) 261 | salt := make([]byte, 16) 262 | c, _ := NewCipher(key) 263 | for i := 0; i < b.N; i++ { 264 | expandKeyWithSalt(key, salt, c) 265 | } 266 | } 267 | 268 | func BenchmarkExpandKey(b *testing.B) { 269 | key := make([]byte, 32) 270 | c, _ := NewCipher(key) 271 | for i := 0; i < b.N; i++ { 272 | ExpandKey(key, c) 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /lib/crypto/blowfish/cipher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package blowfish implements Bruce Schneier's Blowfish encryption algorithm. 6 | package blowfish 7 | 8 | // The code is a port of Bruce Schneier's C implementation. 9 | // See http://www.schneier.com/blowfish.html. 10 | 11 | import "strconv" 12 | 13 | // The Blowfish block size in bytes. 14 | const BlockSize = 8 15 | 16 | // A Cipher is an instance of Blowfish encryption using a particular key. 17 | type Cipher struct { 18 | p [18]uint32 19 | s0, s1, s2, s3 [256]uint32 20 | } 21 | 22 | type KeySizeError int 23 | 24 | func (k KeySizeError) Error() string { 25 | return "crypto/blowfish: invalid key size " + strconv.Itoa(int(k)) 26 | } 27 | 28 | // NewCipher creates and returns a Cipher. 29 | // The key argument should be the Blowfish key, from 1 to 56 bytes. 30 | func NewCipher(key []byte) (*Cipher, error) { 31 | var result Cipher 32 | if k := len(key); k < 1 || k > 56 { 33 | return nil, KeySizeError(k) 34 | } 35 | initCipher(&result) 36 | ExpandKey(key, &result) 37 | return &result, nil 38 | } 39 | 40 | // NewSaltedCipher creates a returns a Cipher that folds a salt into its key 41 | // schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is 42 | // sufficient and desirable. For bcrypt compatiblity, the key can be over 56 43 | // bytes. 44 | func NewSaltedCipher(key, salt []byte) (*Cipher, error) { 45 | if len(salt) == 0 { 46 | return NewCipher(key) 47 | } 48 | var result Cipher 49 | if k := len(key); k < 1 { 50 | return nil, KeySizeError(k) 51 | } 52 | initCipher(&result) 53 | expandKeyWithSalt(key, salt, &result) 54 | return &result, nil 55 | } 56 | 57 | // BlockSize returns the Blowfish block size, 8 bytes. 58 | // It is necessary to satisfy the Block interface in the 59 | // package "crypto/cipher". 60 | func (c *Cipher) BlockSize() int { return BlockSize } 61 | 62 | // Encrypt encrypts the 8-byte buffer src using the key k 63 | // and stores the result in dst. 64 | // Note that for amounts of data larger than a block, 65 | // it is not safe to just call Encrypt on successive blocks; 66 | // instead, use an encryption mode like CBC (see crypto/cipher/cbc.go). 67 | func (c *Cipher) Encrypt(dst, src []byte) { 68 | l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) 69 | r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) 70 | l, r = encryptBlock(l, r, c) 71 | dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) 72 | dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) 73 | } 74 | 75 | // Decrypt decrypts the 8-byte buffer src using the key k 76 | // and stores the result in dst. 77 | func (c *Cipher) Decrypt(dst, src []byte) { 78 | l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) 79 | r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) 80 | l, r = decryptBlock(l, r, c) 81 | dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l) 82 | dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r) 83 | } 84 | 85 | func initCipher(c *Cipher) { 86 | copy(c.p[0:], p[0:]) 87 | copy(c.s0[0:], s0[0:]) 88 | copy(c.s1[0:], s1[0:]) 89 | copy(c.s2[0:], s2[0:]) 90 | copy(c.s3[0:], s3[0:]) 91 | } 92 | -------------------------------------------------------------------------------- /lib/rfweb/app.go: -------------------------------------------------------------------------------- 1 | package rfweb 2 | 3 | import ( 4 | "net/http" 5 | "path" 6 | ) 7 | 8 | const ( 9 | GET = "GET" 10 | POST = "POST" 11 | PUT = "PUT" 12 | DELETE = "DELETE" 13 | HEAD = "HEAD" 14 | PATCH = "PATCH" 15 | OPTIONS = "OPTIONS" 16 | ) 17 | 18 | type App struct { 19 | UrlPrefix string 20 | 21 | rt RouteTable 22 | } 23 | 24 | func NewApp(urlPrefix string) *App { 25 | app := &App{} 26 | app.UrlPrefix = urlPrefix 27 | app.rt = RouteTable{} 28 | return app 29 | } 30 | 31 | func (self *App) Resource(urlPath string, resource Resourcer) { 32 | self.rt.Map( 33 | path.Join(self.UrlPrefix, urlPath), 34 | resource) 35 | } 36 | 37 | func (self *App) ServeHTTP(w http.ResponseWriter, r *http.Request) { 38 | route, params, matched := self.rt.Match(r.URL.Path) 39 | if !matched { 40 | http.NotFound(w, r) 41 | return 42 | } 43 | ctx := NewContext(w, r) 44 | ctx.UrlParams = params 45 | if !route.Resource.OnHandleBegin(ctx) { 46 | return 47 | } 48 | switch r.Method { 49 | case GET: 50 | route.Resource.Get(ctx) 51 | case POST: 52 | route.Resource.Post(ctx) 53 | case PUT: 54 | route.Resource.Put(ctx) 55 | case DELETE: 56 | route.Resource.Delete(ctx) 57 | case PATCH: 58 | route.Resource.Patch(ctx) 59 | case OPTIONS: 60 | route.Resource.Options(ctx) 61 | case HEAD: 62 | route.Resource.Head(ctx) 63 | default: 64 | http.NotFound(w, r) 65 | } 66 | route.Resource.OnHandleEnd(ctx) 67 | } 68 | -------------------------------------------------------------------------------- /lib/rfweb/resource.go: -------------------------------------------------------------------------------- 1 | package rfweb 2 | 3 | import ( 4 | "net/http" 5 | 6 | "aproxy/lib/rfweb/session" 7 | ) 8 | 9 | type Context struct { 10 | W http.ResponseWriter 11 | R *http.Request 12 | UrlParams map[string]string 13 | Data map[interface{}]interface{} 14 | 15 | session *session.Session 16 | } 17 | 18 | func (self Context) Get(key string) string { 19 | res := self.UrlParams[key] 20 | if res == "" { 21 | res = self.R.FormValue(key) 22 | } 23 | return res 24 | } 25 | 26 | func (self Context) Session() *session.Session { 27 | if self.session == nil { 28 | self.session, _ = session.GetSession(self.W, self.R) 29 | } 30 | return self.session 31 | } 32 | 33 | func NewContext(w http.ResponseWriter, r *http.Request) *Context { 34 | ctx := &Context{} 35 | ctx.W = w 36 | ctx.R = r 37 | ctx.UrlParams = map[string]string{} 38 | ctx.Data = make(map[interface{}]interface{}) 39 | return ctx 40 | } 41 | 42 | type Resourcer interface { 43 | Get(*Context) 44 | Post(*Context) 45 | Put(*Context) 46 | Delete(*Context) 47 | Head(*Context) 48 | Patch(*Context) 49 | Options(*Context) 50 | 51 | // before handle request, 52 | // will execute this first. 53 | // if return false, will stop handle the request, 54 | // and end the http request. 55 | OnHandleBegin(*Context) bool 56 | OnHandleEnd(*Context) 57 | } 58 | 59 | type BaseResource struct { 60 | } 61 | 62 | func (self *BaseResource) Get(ctx *Context) { 63 | http.NotFound(ctx.W, ctx.R) 64 | } 65 | 66 | func (self *BaseResource) Put(ctx *Context) { 67 | http.NotFound(ctx.W, ctx.R) 68 | } 69 | 70 | func (self *BaseResource) Post(ctx *Context) { 71 | http.NotFound(ctx.W, ctx.R) 72 | } 73 | 74 | func (self *BaseResource) Delete(ctx *Context) { 75 | http.NotFound(ctx.W, ctx.R) 76 | } 77 | 78 | func (self *BaseResource) Head(ctx *Context) { 79 | http.NotFound(ctx.W, ctx.R) 80 | } 81 | 82 | func (self *BaseResource) Patch(ctx *Context) { 83 | http.NotFound(ctx.W, ctx.R) 84 | } 85 | 86 | func (self *BaseResource) Options(ctx *Context) { 87 | http.NotFound(ctx.W, ctx.R) 88 | } 89 | 90 | func (self *BaseResource) OnHandleBegin(ctx *Context) bool { 91 | return true 92 | } 93 | func (self *BaseResource) OnHandleEnd(ctx *Context) { 94 | 95 | } 96 | -------------------------------------------------------------------------------- /lib/rfweb/route.go: -------------------------------------------------------------------------------- 1 | package rfweb 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | //"path" 7 | 8 | "aproxy/lib/util" 9 | ) 10 | 11 | var ( 12 | regPathParse *regexp.Regexp = regexp.MustCompile("/?\\{[\\w\\-_]+\\}") // matched like this: /{controller} 13 | ) 14 | 15 | // Route config 16 | // var rt = &Route { 17 | // Pattern: "/users/{id}" 18 | // } 19 | // and then, must init the router 20 | // rt.Init() 21 | // and then, you can use it 22 | // rt.Match("/users/1") 23 | // 24 | type Route struct { 25 | Pattern string // url pattern config, eg. /users/{id} 26 | Resource Resourcer 27 | 28 | rePath *regexp.Regexp 29 | inited bool 30 | } 31 | 32 | func (self *Route) Init() { 33 | if self.inited { 34 | return 35 | } 36 | if self.Pattern == "" { 37 | panic("Route: Pattern must be set") 38 | } 39 | if self.Resource == nil { 40 | panic("Route: Resource must be set") 41 | } 42 | 43 | r := regPathParse.ReplaceAllStringFunc(self.Pattern, func(s string) string { 44 | slash := "" 45 | if s[0] == '/' { 46 | slash = "/" 47 | s = s[1:] 48 | } 49 | name, reg, need := s[1:len(s)-1], "[^\\?#/]+", "?" 50 | if slash != "" { 51 | // / => /? 52 | slash = slash + "?" 53 | } 54 | //(?Pre) 55 | return fmt.Sprintf("%s(?P<%s>%s)%s", slash, name, reg, need) 56 | }) 57 | if r != "" && r[len(r)-1] == '/' { 58 | r = r + "?" 59 | } 60 | self.rePath = regexp.MustCompile("^" + r + "$") 61 | self.inited = true 62 | } 63 | 64 | func (self *Route) Match(url string) (params map[string]string, matched bool) { 65 | if !self.inited { 66 | self.Init() 67 | } 68 | 69 | params, matched = util.NamedRegexpGroup(url, self.rePath) 70 | 71 | return 72 | } 73 | 74 | type RouteTable struct { 75 | Routes []*Route 76 | } 77 | 78 | func (rt *RouteTable) Match(url string) (route *Route, params map[string]string, matched bool) { 79 | if url == "" { 80 | return 81 | } 82 | for _, route = range rt.Routes { 83 | params, matched = route.Match(url) 84 | if matched { 85 | return 86 | } 87 | } 88 | return 89 | } 90 | 91 | func (rt *RouteTable) AddRoute(route *Route) { 92 | route.Init() 93 | rt.Routes = append(rt.Routes, route) 94 | } 95 | 96 | // add a new route 97 | func (rt *RouteTable) Map(url string, resource Resourcer) { 98 | 99 | route := &Route{ 100 | Pattern: url, 101 | Resource: resource, 102 | } 103 | route.Init() 104 | rt.Routes = append(rt.Routes, route) 105 | } 106 | -------------------------------------------------------------------------------- /lib/rfweb/route_test.go: -------------------------------------------------------------------------------- 1 | package rfweb 2 | 3 | import ( 4 | "testing" 5 | 6 | . "gopkg.in/bluesuncorp/assert.v1" 7 | ) 8 | 9 | var rs = BaseResource{} 10 | 11 | var r1 = &Route{ 12 | Pattern: "/users/{id}", 13 | Resource: rs, 14 | } 15 | 16 | var r2 = &Route{ 17 | Pattern: "/backends/{hostname}", 18 | Resource: rs, 19 | } 20 | 21 | var r3 = &Route{ 22 | Pattern: "/pages/{name}", 23 | Resource: rs, 24 | } 25 | 26 | func initRoute() { 27 | r1.Init() 28 | r2.Init() 29 | r3.Init() 30 | } 31 | 32 | var routerTestData = []struct { 33 | Route *Route 34 | Url string 35 | Matched bool 36 | Params map[string]string 37 | }{ 38 | { 39 | r1, 40 | "/users/3", 41 | true, 42 | map[string]string{ 43 | "id": "3", 44 | }, 45 | }, 46 | { 47 | r1, 48 | "/post/3", 49 | false, 50 | nil, 51 | }, 52 | { 53 | r2, 54 | "/backends/abc.com", 55 | true, 56 | map[string]string{ 57 | "hostname": "abc.com", 58 | }, 59 | }, 60 | { 61 | r3, 62 | "/pages/about", 63 | true, 64 | map[string]string{ 65 | "name": "about", 66 | }, 67 | }, 68 | } 69 | 70 | func TestRouteInit(t *testing.T) { 71 | defer func() { 72 | if x := recover(); x != nil { 73 | t.Errorf("Must no panic, but got panic: \n\t%s", x) 74 | } 75 | }() 76 | route := new(Route) 77 | route.Pattern = "/users/{id}" 78 | route.Resource = rs 79 | route.Init() 80 | 81 | params, ok := route.Match("/users/2") 82 | Equal(t, ok, true) 83 | Equal(t, params["id"], "2") 84 | } 85 | 86 | func TestRouteMatch(t *testing.T) { 87 | initRoute() 88 | 89 | for _, td := range routerTestData { 90 | params, ok := td.Route.Match(td.Url) 91 | if ok != td.Matched { 92 | t.Errorf("url [%s] not match", td.Url) 93 | } 94 | Equal(t, ok, td.Matched) 95 | if td.Matched && ok { 96 | for k, p := range td.Params { 97 | Equal(t, params[k], p) 98 | } 99 | } 100 | } 101 | } 102 | 103 | func TestRouteTable(t *testing.T) { 104 | var rt *RouteTable 105 | rt = &RouteTable{Routes: make([]*Route, 0, 10)} 106 | 107 | rt.Map("/post/{id}", rs) 108 | rt.AddRoute(r2) 109 | rt.AddRoute(r1) 110 | 111 | route, params, ok := rt.Match("/p") 112 | Equal(t, ok, false) 113 | NotEqual(t, route, nil) 114 | 115 | route, params, ok = rt.Match("/post/save") 116 | Equal(t, ok, true) 117 | Equal(t, params["id"], "save") 118 | NotEqual(t, route, nil) 119 | 120 | route, params, ok = rt.Match("/users/2") 121 | Equal(t, ok, true) 122 | Equal(t, params["id"], "2") 123 | Equal(t, route, r1) 124 | } 125 | -------------------------------------------------------------------------------- /lib/rfweb/session/session.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | // "encoding/base64" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | // "io" 9 | "crypto/rand" 10 | "net/http" 11 | "net/url" 12 | "time" 13 | ) 14 | 15 | var ( 16 | sessionExp = int64(60 * 60 * 24 * 7) 17 | sessionCookieName = "aproxysid" 18 | sessionDomain = "" 19 | ) 20 | 21 | type Session struct { 22 | sessionId string 23 | 24 | storage SessionStorager 25 | } 26 | 27 | func NewSession(sid string) *Session { 28 | s := &Session{} 29 | s.sessionId = sid 30 | s.storage = sessionStorage 31 | return s 32 | } 33 | 34 | func (self *Session) Get(key string) (string, error) { 35 | return self.storage.Get(self.sessionId, key) 36 | } 37 | 38 | func (self *Session) Set(key, val string, exp ...int64) error { 39 | iexp := sessionExp 40 | if len(exp) > 0 && exp[0] > 0 { 41 | iexp = exp[0] 42 | } 43 | return self.storage.Set(self.sessionId, key, val, iexp) 44 | } 45 | 46 | func (self *Session) GetStuct(key string, val interface{}) error { 47 | sval, err := self.Get(key) 48 | if err != nil { 49 | return err 50 | } 51 | if len(sval) < 1 { 52 | return nil 53 | } 54 | err = json.Unmarshal([]byte(sval), val) 55 | return err 56 | } 57 | 58 | func (self *Session) SetStuct(key string, val interface{}, exp ...int64) error { 59 | sval, err := json.Marshal(val) 60 | if err != nil { 61 | return err 62 | } 63 | err = self.Set(key, string(sval), exp...) 64 | return err 65 | } 66 | 67 | func (self *Session) Clear(w http.ResponseWriter) error { 68 | err := self.storage.Clear(self.sessionId) 69 | c := &http.Cookie{ 70 | Name: sessionCookieName, 71 | Expires: time.Now().Add(-10 * time.Second), 72 | Path: "/", 73 | Domain: sessionDomain, 74 | } 75 | http.SetCookie(w, c) 76 | return err 77 | } 78 | 79 | func NewSessionId() (string, error) { 80 | b := make([]byte, 20) 81 | n, err := rand.Read(b) 82 | if n != len(b) || err != nil { 83 | return "", fmt.Errorf("Could not successfully read from the system CSPRNG.") 84 | } 85 | return hex.EncodeToString(b), nil 86 | } 87 | 88 | func WriteSessionId(w http.ResponseWriter, sid string, exp int64) { 89 | if len(sid) == 0 { 90 | return 91 | } 92 | 93 | cookie := &http.Cookie{ 94 | Name: sessionCookieName, 95 | Value: url.QueryEscape(sid), 96 | HttpOnly: true, 97 | Path: "/", 98 | Domain: sessionDomain, 99 | } 100 | if exp > 0 { 101 | expiration := time.Now() 102 | expiration = expiration.Add(time.Second * time.Duration(exp)) 103 | cookie.Expires = expiration 104 | } 105 | http.SetCookie(w, cookie) 106 | } 107 | 108 | func GetSession(w http.ResponseWriter, r *http.Request) (*Session, error) { 109 | sid := "" 110 | sidCookie, err := r.Cookie(sessionCookieName) 111 | if err == nil { 112 | sid, _ = url.QueryUnescape(sidCookie.Value) 113 | } 114 | if sid == "" { 115 | sid, err = NewSessionId() 116 | if err == nil { 117 | WriteSessionId(w, sid, sessionExp) 118 | } 119 | } 120 | s := NewSession(sid) 121 | return s, nil 122 | } 123 | 124 | func InitSessionServer(domain, cookieName string, exp int64) { 125 | sessionDomain = domain 126 | sessionCookieName = cookieName 127 | sessionExp = exp 128 | } 129 | -------------------------------------------------------------------------------- /lib/rfweb/session/session_storage.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | redis "gopkg.in/redis.v5" 9 | ) 10 | 11 | var sessionStorage SessionStorager 12 | 13 | type SessionStorager interface { 14 | Get(sid, key string) (string, error) 15 | // Set key to hold the string value and 16 | // set key to timeout after a given number of seconds. 17 | Set(sid, key, val string, exp int64) error 18 | 19 | Clear(sid string) error 20 | } 21 | 22 | type RedisSessionStorage struct { 23 | Addr string 24 | Password string 25 | DB int 26 | 27 | client *redis.Client 28 | } 29 | 30 | // addr: "127.0.0.1:6379" 31 | func NewRedisSessionStorage(addr, pwd string, db int) (*RedisSessionStorage, error) { 32 | ss := &RedisSessionStorage{ 33 | Addr: addr, 34 | Password: pwd, 35 | DB: db, 36 | } 37 | ss.client = redis.NewClient(&redis.Options{ 38 | Addr: addr, 39 | Password: pwd, 40 | DB: db, 41 | IdleTimeout: 3 * time.Minute, 42 | }) 43 | _, err := ss.client.Ping().Result() 44 | if err != nil { 45 | return nil, err 46 | } 47 | // // keep-alive 48 | // go func() { 49 | // for { 50 | // ss.client.Ping() 51 | // time.Sleep(10 * time.Second) 52 | // } 53 | // }() 54 | return ss, nil 55 | } 56 | 57 | func (self *RedisSessionStorage) Get(sid, key string) (string, error) { 58 | val, err := self.client.HGet(sid, key).Result() 59 | if err != nil { 60 | if err == redis.Nil { 61 | err = nil 62 | } else { 63 | err = errors.New("redis hget error: " + err.Error()) 64 | } 65 | } 66 | return val, err 67 | } 68 | 69 | func (self *RedisSessionStorage) Set(sid, key, val string, expiration int64) error { 70 | _, err := self.client.HSet(sid, key, val).Result() 71 | if err == nil && expiration > 0 { 72 | self.client.Expire(sid, time.Duration(expiration)*time.Second) 73 | } 74 | return err 75 | } 76 | 77 | func (self *RedisSessionStorage) Clear(sid string) error { 78 | r := self.client.Del(sid) 79 | if r.Err() != nil { 80 | return r.Err() 81 | } 82 | return nil 83 | } 84 | 85 | // 86 | // 87 | 88 | func SetSessionStorager(ss SessionStorager) { 89 | if ss == nil { 90 | panic("[SetSessionStorager] SessionStorager must not nil.") 91 | } 92 | sessionStorage = ss 93 | } 94 | 95 | func SetSessionStoragerToRedis(addr, pwd string, db int) error { 96 | ss, err := NewRedisSessionStorage(addr, pwd, db) 97 | if err != nil { 98 | return fmt.Errorf("[SetSessionStoragerToRedis] faild: %s", err.Error()) 99 | } 100 | sessionStorage = ss 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /lib/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "regexp" 9 | 10 | "aproxy/lib/crypto/bcrypt" 11 | ) 12 | 13 | func DecodeJsonBody(r io.ReadCloser, to interface{}) error { 14 | defer r.Close() 15 | err := json.NewDecoder(r).Decode(to) 16 | return err 17 | } 18 | 19 | // return json string result 20 | // WriteJson(obj) or WriteJson(obj, "text/html") 21 | func WriteJson(w http.ResponseWriter, data interface{}, contentType ...string) { 22 | var ct string 23 | if len(contentType) == 1 { 24 | ct = contentType[0] 25 | } else { 26 | ct = "application/json" 27 | } 28 | w.Header().Set("Content-Type", ct) 29 | s := "" 30 | b, err := json.Marshal(data) 31 | if err != nil { 32 | s = `{success:false, message:"json.Marshal error"}` 33 | } else { 34 | s = string(b) 35 | } 36 | fmt.Fprint(w, s) 37 | } 38 | 39 | // match regexp with string, and return a named group map 40 | // Example: 41 | // regexp: "(?P[A-Za-z]+)-(?P\\d+)" 42 | // string: "CGC-30" 43 | // return: map[string]string{ "name":"CGC", "age":"30" } 44 | func NamedRegexpGroup(str string, reg *regexp.Regexp) (ng map[string]string, matched bool) { 45 | rst := reg.FindStringSubmatch(str) 46 | //fmt.Printf("%s => %s => %s\n\n", reg, str, rst) 47 | if len(rst) < 1 { 48 | return 49 | } 50 | ng = make(map[string]string) 51 | lenRst := len(rst) 52 | sn := reg.SubexpNames() 53 | for k, v := range sn { 54 | // SubexpNames contain the none named group, 55 | // so must filter v == "" 56 | if k == 0 || v == "" { 57 | continue 58 | } 59 | if k+1 > lenRst { 60 | break 61 | } 62 | ng[v] = rst[k] 63 | } 64 | matched = true 65 | return 66 | } 67 | 68 | func CryptPassword(password []byte) ([]byte, error) { 69 | return bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) 70 | } 71 | 72 | func CompareHashAndPassword(hashedPassword, password []byte) error { 73 | return bcrypt.CompareHashAndPassword(hashedPassword, password) 74 | } 75 | -------------------------------------------------------------------------------- /loginservices/github/oauth.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "errors" 7 | "net/http" 8 | "path" 9 | 10 | "github.com/google/go-github/github" 11 | "golang.org/x/oauth2" 12 | ) 13 | 14 | const ( 15 | githubAuthorizeUrl = "https://github.com/login/oauth/authorize" 16 | githubTokenUrl = "https://github.com/login/oauth/access_token" 17 | ) 18 | 19 | var ( 20 | oauthCfg *oauth2.Config 21 | redirectUrl string 22 | scopes []string 23 | ) 24 | 25 | func init() { 26 | scopes = []string{"user:email"} 27 | } 28 | 29 | type GithubOauther struct { 30 | } 31 | 32 | func (self GithubOauther) Providers() []string { 33 | return []string{ 34 | "Github", 35 | } 36 | } 37 | 38 | func (self GithubOauther) Login(providerName string, w http.ResponseWriter, r *http.Request) error { 39 | b := make([]byte, 16) 40 | rand.Read(b) 41 | state := base64.URLEncoding.EncodeToString(b) 42 | 43 | authUrl := oauthCfg.AuthCodeURL(state) 44 | // redirect 45 | http.Redirect(w, r, authUrl, http.StatusFound) 46 | return nil 47 | } 48 | 49 | func (self GithubOauther) Callback(providerName string, w http.ResponseWriter, r *http.Request) (string, error) { 50 | tkn, err := oauthCfg.Exchange(oauth2.NoContext, r.URL.Query().Get("code")) 51 | if err != nil { 52 | return "", errors.New("there was an issue getting your token: " + err.Error()) 53 | } 54 | 55 | if !tkn.Valid() { 56 | return "", errors.New("Github oauth retreived invalid token." + err.Error()) 57 | } 58 | 59 | client := github.NewClient(oauthCfg.Client(oauth2.NoContext, tkn)) 60 | // opt := &github.ListOptions{} 61 | emails, _, err := client.Users.ListEmails(oauth2.NoContext, nil) 62 | if err != nil { 63 | return "", errors.New("get github email faild: " + err.Error()) 64 | } 65 | email := "" 66 | if len(emails) > 0 { 67 | email = emails[0].GetEmail() 68 | } 69 | 70 | return email, nil 71 | } 72 | 73 | // @host: include http://, like http://abc.com 74 | func InitGithubOauther(urlPrefix, host, clientID, clientSecret string) { 75 | redirectUrl = host + path.Join(urlPrefix, "/api/oauth/callback?provider=github") 76 | oauthCfg = &oauth2.Config{ 77 | ClientID: clientID, 78 | ClientSecret: clientSecret, 79 | Endpoint: oauth2.Endpoint{ 80 | AuthURL: githubAuthorizeUrl, 81 | TokenURL: githubTokenUrl, 82 | }, 83 | RedirectURL: redirectUrl, 84 | Scopes: scopes, 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /module/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "log" 5 | 6 | "aproxy/lib/rfweb" 7 | "aproxy/module/constant" 8 | ) 9 | 10 | // check permission, return error code: 11 | // 0: ok 12 | // 1: need login 13 | // 2: do not has permission 14 | func CheckPermission(authType int, ctx *rfweb.Context) int { 15 | if authType <= constant.AUTH_TYPE_PUBLIC { 16 | return constant.PERMISSION_STATUS_OK 17 | } 18 | user := GetLoginedUser(ctx) 19 | if user == nil { 20 | return constant.PERMISSION_STATUS_NEED_LOGIN 21 | } 22 | if authType == constant.AUTH_TYPE_LOGIN { 23 | return constant.PERMISSION_STATUS_OK 24 | } 25 | // TODO: need cache 26 | authority, err := GetAuthorityByEmail(user.Email) 27 | if err != nil || authority == nil { 28 | return constant.PERMISSION_STATUS_NO_PERMISSION 29 | } 30 | authority.Init() 31 | rurl := ctx.R.Host + ctx.R.RequestURI 32 | if !authority.HasPermission(rurl) { 33 | return constant.PERMISSION_STATUS_NO_PERMISSION 34 | } 35 | return constant.PERMISSION_STATUS_OK 36 | } 37 | 38 | func GetLoginedUser(ctx *rfweb.Context) *User { 39 | r, ok := ctx.Data[constant.CTX_KEY_USER] 40 | if ok && r != nil { 41 | return r.(*User) 42 | } 43 | session := ctx.Session() 44 | var user User 45 | err := session.GetStuct(constant.SS_KEY_USER, &user) 46 | if err != nil { 47 | log.Println("[ERROR] get session error: ", err) 48 | return nil 49 | } 50 | if len(user.Id) > 0 { 51 | ctx.Data[constant.CTX_KEY_USER] = &user 52 | } else { 53 | return nil 54 | } 55 | return &user 56 | } 57 | -------------------------------------------------------------------------------- /module/auth/authority.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "regexp" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "gopkg.in/mgo.v2" 12 | "gopkg.in/mgo.v2/bson" 13 | 14 | "aproxy/module/db" 15 | ) 16 | 17 | const ( 18 | C_NAME_Authority = "Authority" 19 | ) 20 | 21 | type Authority struct { 22 | sync.RWMutex `bson:"-"` 23 | 24 | Id string `bson:"_id,omitempty"` 25 | Email string `bson:"Email"` 26 | Desc string `bson:"Desc"` // description 27 | Roles []string `bson:"Roles"` 28 | Allow []string `bson:"Allow"` 29 | Deny []string `bson:"Deny"` 30 | 31 | // 0: not a administrator 32 | // 50: system administrator 33 | // 99: super administrator 34 | // Currently used to determine whether 35 | // there set permissions, this field is 36 | // mainly reserved for future expansion. 37 | AdminLevel int `bson:"AdminLevel"` 38 | 39 | CreatedTime time.Time `bson:"CreatedTime"` 40 | UpdatedTime time.Time `bson:"UpdatedTime"` 41 | 42 | inited bool 43 | rl, al, dl int 44 | allowReg []*regexp.Regexp 45 | denyReg []*regexp.Regexp 46 | roles []*Role 47 | } 48 | 49 | func (self *Authority) Init() { 50 | self.Lock() 51 | defer self.Unlock() 52 | if self.inited { 53 | return 54 | } 55 | self.init() 56 | } 57 | 58 | func (self *Authority) init() { 59 | if self.allowReg == nil { 60 | self.allowReg = []*regexp.Regexp{} 61 | } 62 | if self.denyReg == nil { 63 | self.denyReg = []*regexp.Regexp{} 64 | } 65 | for _, allow := range self.Allow { 66 | allow = strings.Replace(allow, ".", "\\.", -1) 67 | allow = strings.Replace(allow, "*", ".*", -1) 68 | self.allowReg = append(self.allowReg, 69 | regexp.MustCompile(allow)) 70 | } 71 | for _, deny := range self.Deny { 72 | deny = strings.Replace(deny, ".", "\\.", -1) 73 | deny = strings.Replace(deny, "*", ".*", -1) 74 | self.denyReg = append(self.denyReg, 75 | regexp.MustCompile(deny)) 76 | } 77 | for _, id := range self.Roles { 78 | // TODO: need cache 79 | r, err := GetRoleByID(id) 80 | if err == nil && r != nil && r.Id != "" { 81 | // must init role 82 | r.Init() 83 | self.roles = append(self.roles, r) 84 | } 85 | } 86 | self.al = len(self.allowReg) 87 | self.dl = len(self.denyReg) 88 | self.rl = len(self.roles) 89 | return 90 | } 91 | 92 | // must Init() before use this. 93 | func (self *Authority) HasPermission(url string) bool { 94 | for i := 0; i < self.dl; i++ { 95 | if self.denyReg[i].MatchString(url) { 96 | return false 97 | } 98 | } 99 | for i := 0; i < self.al; i++ { 100 | if self.allowReg[i].MatchString(url) { 101 | return true 102 | } 103 | } 104 | for i := 0; i < self.rl; i++ { 105 | if self.roles[i].HasPermission(url) { 106 | return true 107 | } 108 | } 109 | return false 110 | } 111 | 112 | func GetAuthorityByID(id string) (*Authority, error) { 113 | c := db.MDB().C(C_NAME_Authority) 114 | var r Authority 115 | err := c.Find(bson.M{"_id": id}).One(&r) 116 | if err != nil { 117 | if err != mgo.ErrNotFound { 118 | log.Printf("Get Authority [%s] Error: %s", id, err) 119 | } 120 | return &r, err 121 | } 122 | return &r, nil 123 | } 124 | 125 | // if not found, will return (nil, nil) 126 | func GetAuthorityByEmail(email string) (*Authority, error) { 127 | email = strings.ToLower(email) 128 | c := db.MDB().C(C_NAME_Authority) 129 | var r *Authority 130 | err := c.Find(bson.M{"Email": email}).One(&r) 131 | if err != nil { 132 | if err != mgo.ErrNotFound { 133 | log.Printf("Get Authority [%s] Error: %s", email, err) 134 | } else { 135 | err = nil 136 | } 137 | return r, err 138 | } 139 | return r, nil 140 | } 141 | 142 | func GetAllAuthority() ([]Authority, error) { 143 | c := db.MDB().C(C_NAME_Authority) 144 | rs := []Authority{} 145 | err := c.Find(nil).All(&rs) 146 | if err != nil { 147 | log.Printf("GetAllAuthority Error: %s", err) 148 | return rs, err 149 | } 150 | return rs, nil 151 | } 152 | 153 | func InsertAuthority(a *Authority) error { 154 | a.Email = strings.ToLower(a.Email) 155 | ea, err := GetAuthorityByEmail(a.Email) 156 | if err != nil { 157 | return fmt.Errorf("check authority error: %s", err.Error()) 158 | } 159 | if ea != nil && ea.Email == a.Email { 160 | return fmt.Errorf("authority for [%s] existed.", a.Email) 161 | } 162 | c := db.MDB().C(C_NAME_Authority) 163 | a.Id = bson.NewObjectId().Hex() 164 | err = c.Insert(a) 165 | return err 166 | } 167 | 168 | func UpdateAuthority(id string, a *Authority) error { 169 | c := db.MDB().C(C_NAME_Authority) 170 | change := bson.M{"$set": bson.M{ 171 | "Desc": a.Desc, 172 | "AdminLevel": a.AdminLevel, 173 | "Roles": a.Roles, 174 | "Allow": a.Allow, 175 | "Deny": a.Deny, 176 | "UpdatedTime": time.Now()}} 177 | err := c.UpdateId(id, change) 178 | return err 179 | } 180 | 181 | func DeleteAuthority(id string) error { 182 | c := db.MDB().C(C_NAME_Authority) 183 | err := c.RemoveId(id) 184 | return err 185 | } 186 | -------------------------------------------------------------------------------- /module/auth/login/login.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "path" 7 | "strings" 8 | 9 | "aproxy/lib/rfweb" 10 | "aproxy/lib/util" 11 | "aproxy/module/auth" 12 | "aproxy/module/constant" 13 | ) 14 | 15 | var loginUrl = "" 16 | var loginHost = "" 17 | 18 | type RespData struct { 19 | Success bool `json:"success"` 20 | Error string `json:"error"` 21 | Data interface{} `json:"data"` 22 | } 23 | 24 | type LoginResource struct { 25 | rfweb.BaseResource 26 | } 27 | 28 | func (self LoginResource) Post(ctx *rfweb.Context) { 29 | res := RespData{} 30 | email := strings.ToLower(ctx.Get("email")) 31 | email = strings.TrimSpace(email) 32 | pwd := strings.TrimSpace(ctx.Get("pwd")) 33 | // remember := ctx.Get("remember") 34 | 35 | user, err := auth.LoginUser(email, pwd) 36 | if err != nil { 37 | res.Error = err.Error() 38 | } else { 39 | res.Success = true 40 | // res.Data = ctx.Get("returnurl") 41 | session := ctx.Session() 42 | session.SetStuct(constant.SS_KEY_USER, user) 43 | } 44 | util.WriteJson(ctx.W, res) 45 | } 46 | 47 | func redirectToLogin(w http.ResponseWriter, r *http.Request, hasReturnUrl bool) { 48 | scheme := "http://" 49 | if r.TLS != nil { 50 | scheme = "https://" 51 | } 52 | returnurl := "" 53 | if hasReturnUrl { 54 | returnurl = scheme + r.Host + r.RequestURI 55 | } 56 | tourl := loginUrl + "?returnurl=" + url.QueryEscape(returnurl) 57 | w.Header().Set("Location", tourl) 58 | w.WriteHeader(http.StatusFound) 59 | } 60 | 61 | func RedirectToLogin(w http.ResponseWriter, r *http.Request) { 62 | redirectToLogin(w, r, true) 63 | } 64 | 65 | func InitLoginServer(host, urlPrefix string) { 66 | if len(host) > 0 && string(host[len(host)-1]) == "/" { 67 | host = host[:len(host)-1] 68 | } 69 | loginUrl = host + path.Join("/", urlPrefix, "login.html") 70 | r, err := url.Parse(loginUrl) 71 | if err == nil { 72 | loginHost = r.Host 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /module/auth/login/logout.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "aproxy/lib/rfweb" 5 | ) 6 | 7 | type LogoutResource struct { 8 | rfweb.BaseResource 9 | } 10 | 11 | func (self *LogoutResource) Get(ctx *rfweb.Context) { 12 | session := ctx.Session() 13 | session.Clear(ctx.W) 14 | redirectToLogin(ctx.W, ctx.R, false) 15 | } 16 | -------------------------------------------------------------------------------- /module/auth/login/oauth.go: -------------------------------------------------------------------------------- 1 | package login 2 | 3 | import ( 4 | "aproxy/lib/rfweb" 5 | "aproxy/lib/util" 6 | "aproxy/module/auth" 7 | "aproxy/module/constant" 8 | "aproxy/module/oauth" 9 | "io" 10 | "net/http" 11 | ) 12 | 13 | type OauthListResource struct { 14 | rfweb.BaseResource 15 | } 16 | 17 | func (self OauthListResource) Get(ctx *rfweb.Context) { 18 | res := RespData{} 19 | 20 | providers := oauth.GetProviderNameList() 21 | res.Success = true 22 | res.Data = providers 23 | 24 | util.WriteJson(ctx.W, res) 25 | } 26 | 27 | type OauthLoginResource struct { 28 | rfweb.BaseResource 29 | } 30 | 31 | func (self OauthLoginResource) Get(ctx *rfweb.Context) { 32 | returnurl := ctx.Get("returnurl") 33 | if returnurl != "" { 34 | ctx.Session().Set("returnurl", returnurl) 35 | } 36 | providerName := ctx.Get("provider") 37 | provider := oauth.GetOauther(providerName) 38 | if provider == nil { 39 | http.Error(ctx.W, "Can't find oauth provider.", http.StatusForbidden) 40 | return 41 | } 42 | err := provider.Login(providerName, ctx.W, ctx.R) 43 | if err != nil { 44 | http.Error(ctx.W, "oAuth login faild: "+err.Error(), http.StatusInternalServerError) 45 | } 46 | } 47 | 48 | type OauthCallbackResource struct { 49 | rfweb.BaseResource 50 | } 51 | 52 | func (self OauthCallbackResource) Get(ctx *rfweb.Context) { 53 | providerName := ctx.Get("provider") 54 | provider := oauth.GetOauther(providerName) 55 | if provider == nil { 56 | http.Error(ctx.W, "Can't find oauth provider.", http.StatusForbidden) 57 | return 58 | } 59 | email, err := provider.Callback(providerName, ctx.W, ctx.R) 60 | if err != nil { 61 | http.Error(ctx.W, "oAuth login faild: "+err.Error(), http.StatusInternalServerError) 62 | return 63 | } 64 | if email == "" { 65 | http.Error(ctx.W, "oAuth login get email faild.", http.StatusInternalServerError) 66 | return 67 | } 68 | user := &auth.User{} 69 | user.Name = email 70 | user.Email = email 71 | user.Id = email 72 | 73 | session := ctx.Session() 74 | err = session.SetStuct(constant.SS_KEY_USER, user) 75 | if err != nil { 76 | http.Error(ctx.W, "set user info to session faild: "+err.Error(), http.StatusInternalServerError) 77 | return 78 | } 79 | 80 | returnurl, _ := session.Get("returnurl") 81 | if returnurl != "" { 82 | ctx.W.Header().Set("Location", returnurl) 83 | ctx.W.WriteHeader(http.StatusFound) 84 | } else { 85 | io.WriteString(ctx.W, "Login success with "+email) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /module/auth/role.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | // "fmt" 5 | "log" 6 | "regexp" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "gopkg.in/mgo.v2" 12 | "gopkg.in/mgo.v2/bson" 13 | 14 | "aproxy/module/db" 15 | ) 16 | 17 | const ( 18 | C_NAME_Role = "Role" 19 | ) 20 | 21 | type Role struct { 22 | sync.RWMutex `bson:"-"` 23 | 24 | Id string `bson:"_id,omitempty"` 25 | Name string `bson:"Name"` 26 | Desc string `bson:"Desc"` 27 | Allow []string `bson:"Allow"` 28 | Deny []string `bson:"Deny"` 29 | 30 | CreatedTime time.Time `bson:"CreatedTime"` 31 | UpdatedTime time.Time `bson:"UpdatedTime"` 32 | 33 | inited bool 34 | al int 35 | dl int 36 | allowReg []*regexp.Regexp 37 | denyReg []*regexp.Regexp 38 | } 39 | 40 | func (self *Role) Init() { 41 | self.Lock() 42 | defer self.Unlock() 43 | if self.inited { 44 | return 45 | } 46 | self.init() 47 | } 48 | 49 | func (self *Role) init() { 50 | if self.allowReg == nil { 51 | self.allowReg = []*regexp.Regexp{} 52 | } 53 | if self.denyReg == nil { 54 | self.denyReg = []*regexp.Regexp{} 55 | } 56 | for _, allow := range self.Allow { 57 | allow = strings.Replace(allow, ".", "\\.", -1) 58 | allow = strings.Replace(allow, "*", ".*", -1) 59 | self.allowReg = append(self.allowReg, 60 | regexp.MustCompile(allow)) 61 | } 62 | for _, deny := range self.Deny { 63 | deny = strings.Replace(deny, ".", "\\.", -1) 64 | deny = strings.Replace(deny, "*", ".*", -1) 65 | self.denyReg = append(self.denyReg, 66 | regexp.MustCompile(deny)) 67 | } 68 | self.al = len(self.allowReg) 69 | self.dl = len(self.denyReg) 70 | return 71 | } 72 | 73 | // must Init() before use this. 74 | func (self *Role) HasPermission(url string) bool { 75 | for i := 0; i < self.dl; i++ { 76 | if self.denyReg[i].MatchString(url) { 77 | return false 78 | } 79 | } 80 | for i := 0; i < self.al; i++ { 81 | if self.allowReg[i].MatchString(url) { 82 | return true 83 | } 84 | } 85 | return false 86 | } 87 | 88 | func GetRoleByID(id string) (*Role, error) { 89 | c := db.MDB().C(C_NAME_Role) 90 | var r Role 91 | err := c.Find(bson.M{"_id": id}).One(&r) 92 | if err != nil { 93 | if err != mgo.ErrNotFound { 94 | log.Printf("Get Role [%s] Error: %s", id, err) 95 | } 96 | return &r, err 97 | } 98 | return &r, nil 99 | } 100 | 101 | func GetAllRole() ([]Role, error) { 102 | c := db.MDB().C(C_NAME_Role) 103 | rs := []Role{} 104 | err := c.Find(nil).All(&rs) 105 | if err != nil { 106 | log.Printf("GetAllRole Error: %s", err) 107 | return rs, err 108 | } 109 | return rs, nil 110 | } 111 | 112 | func InsertRole(role *Role) error { 113 | c := db.MDB().C(C_NAME_Role) 114 | role.Id = bson.NewObjectId().Hex() 115 | err := c.Insert(role) 116 | return err 117 | } 118 | 119 | func UpdateRole(id string, role *Role) error { 120 | c := db.MDB().C(C_NAME_Role) 121 | change := bson.M{"$set": bson.M{ 122 | "Name": role.Name, 123 | "Desc": role.Desc, 124 | "Allow": role.Allow, 125 | "Deny": role.Deny, 126 | "UpdatedTime": time.Now()}} 127 | err := c.UpdateId(id, change) 128 | return err 129 | } 130 | 131 | func DeleteRole(id string) error { 132 | c := db.MDB().C(C_NAME_Role) 133 | err := c.RemoveId(id) 134 | return err 135 | } 136 | -------------------------------------------------------------------------------- /module/auth/role_test.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | . "github.com/smartystreets/goconvey/convey" 5 | "testing" 6 | ) 7 | 8 | func TestRole(t *testing.T) { 9 | 10 | r := Role{ 11 | Id: "test-1", 12 | Name: "Test", 13 | Desc: "a test role", 14 | Allow: []string{ 15 | "*.abc.com/allow/*", 16 | "*.allow.com/*", 17 | }, 18 | Deny: []string{ 19 | "*.abc.com/deny/*", 20 | }, 21 | } 22 | r.Init() 23 | 24 | Convey("Access to different urls", t, func() { 25 | 26 | Convey("Deny to access", func() { 27 | So(r.HasPermission("http://hi.abc.com/deny/you.html"), ShouldBeFalse) 28 | So(r.HasPermission("http://unset.com/you.html"), ShouldBeFalse) 29 | }) 30 | Convey("Allow to access", func() { 31 | So(r.HasPermission("http://hi.abc.com/allow/you.html"), ShouldBeTrue) 32 | So(r.HasPermission("http://www.allow.com/you.html"), ShouldBeTrue) 33 | }) 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /module/auth/user.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strings" 8 | "time" 9 | 10 | "gopkg.in/mgo.v2" 11 | "gopkg.in/mgo.v2/bson" 12 | 13 | "aproxy/lib/util" 14 | "aproxy/module/db" 15 | ) 16 | 17 | const ( 18 | C_NAME_User = "User" 19 | ) 20 | 21 | var userStorager UserStorager 22 | 23 | type User struct { 24 | Id string `bson:"_id,omitempty"` 25 | Name string `bson:"Name"` 26 | Email string `bson:"Email"` 27 | Desc string `bson:"Desc"` // description 28 | Pwd string `bson:"Pwd" json:"-"` 29 | 30 | CreatedTime time.Time `bson:"CreatedTime"` 31 | UpdatedTime time.Time `bson:"UpdatedTime"` 32 | } 33 | 34 | type UserStorager interface { 35 | Login(email, pwd string) (*User, error) 36 | GetByEmail(email string) (*User, error) 37 | GetAll() ([]User, error) 38 | // add new user. 39 | // user.Pwd field has encrypted. 40 | Insert(user User) error 41 | Update(id string, user User) error 42 | Delete(id string) error 43 | } 44 | 45 | type MongoUserStorage struct { 46 | } 47 | 48 | func (self *MongoUserStorage) Login(email, pwd string) (*User, error) { 49 | if email == "" || pwd == "" { 50 | return nil, errors.New("please enter email and password.") 51 | } 52 | user, err := self.GetByEmail(email) 53 | if err != nil { 54 | return nil, errors.New("query user by email got error: " + err.Error()) 55 | } 56 | if user == nil || user.Email == "" { 57 | return nil, errors.New("email or password wrong.") 58 | } 59 | err = util.CompareHashAndPassword([]byte(user.Pwd), []byte(pwd)) 60 | if err != nil { 61 | return nil, errors.New("email or password wrong.") 62 | } 63 | return user, nil 64 | } 65 | 66 | func (self *MongoUserStorage) Insert(user User) error { 67 | user.Id = bson.NewObjectId().Hex() 68 | c := db.MDB().C(C_NAME_User) 69 | err := c.Insert(user) 70 | return err 71 | } 72 | 73 | func (self *MongoUserStorage) Update(id string, user User) error { 74 | if len(user.Pwd) < 10 { 75 | return nil 76 | } 77 | c := db.MDB().C(C_NAME_User) 78 | change := bson.M{ 79 | "Pwd": user.Pwd, 80 | "UpdatedTime": time.Now()} 81 | err := c.UpdateId(id, bson.M{"$set": change}) 82 | return err 83 | } 84 | 85 | func (self *MongoUserStorage) Delete(id string) error { 86 | c := db.MDB().C(C_NAME_User) 87 | err := c.RemoveId(id) 88 | return err 89 | } 90 | 91 | func (self *MongoUserStorage) GetByEmail(email string) (*User, error) { 92 | c := db.MDB().C(C_NAME_User) 93 | var user User 94 | err := c.Find(bson.M{"Email": email}).One(&user) 95 | if err != nil { 96 | if err != mgo.ErrNotFound { 97 | log.Printf("MongoUserStorage GetByEmail [%s] Error: %s", email, err) 98 | return nil, err 99 | } else { 100 | return nil, nil 101 | } 102 | } 103 | return &user, nil 104 | } 105 | 106 | func (self *MongoUserStorage) GetAll() ([]User, error) { 107 | c := db.MDB().C(C_NAME_User) 108 | users := []User{} 109 | err := c.Find(nil).All(&users) 110 | if err != nil { 111 | log.Printf("User GetAll Error: %s", err) 112 | return users, err 113 | } 114 | return users, nil 115 | } 116 | 117 | func validUser(user User) error { 118 | return nil 119 | } 120 | 121 | // 122 | // 123 | 124 | func SetUserStorage(us UserStorager) { 125 | if us == nil { 126 | panic("SetUserStorage: UserStorager MUST NOT (nil)!") 127 | } 128 | userStorager = us 129 | } 130 | 131 | func SetUserStorageToMongo() error { 132 | SetUserStorage(&MongoUserStorage{}) 133 | return nil 134 | } 135 | 136 | // 137 | // 138 | 139 | func LoginUser(email, pwd string) (*User, error) { 140 | return userStorager.Login(email, pwd) 141 | } 142 | 143 | func GetUserByEmail(email string) (*User, error) { 144 | email = strings.ToLower(email) 145 | return userStorager.GetByEmail(email) 146 | } 147 | 148 | func GetAllUsers() ([]User, error) { 149 | return userStorager.GetAll() 150 | } 151 | 152 | func InsertUser(user User) error { 153 | err := validUser(user) 154 | if err != nil { 155 | return err 156 | } 157 | euser, err2 := userStorager.GetByEmail(user.Email) 158 | if err2 == nil && euser != nil && euser.Email == user.Email { 159 | return errors.New(fmt.Sprintf("email [%s] has exist.", user.Email)) 160 | } 161 | pwd, err := util.CryptPassword([]byte(user.Pwd)) 162 | if err != nil { 163 | return err 164 | } 165 | user.Pwd = string(pwd) 166 | return userStorager.Insert(user) 167 | } 168 | 169 | func UpdateUser(id string, user User) error { 170 | if len(user.Pwd) > 0 { 171 | pwd, err := util.CryptPassword([]byte(user.Pwd)) 172 | if err != nil { 173 | return err 174 | } 175 | user.Pwd = string(pwd) 176 | } 177 | return userStorager.Update(id, user) 178 | } 179 | 180 | func DeleteUser(id string) error { 181 | if len(id) < 1 { 182 | return errors.New("wrong id") 183 | } 184 | return userStorager.Delete(id) 185 | } 186 | -------------------------------------------------------------------------------- /module/backend_conf/conf_storage.go: -------------------------------------------------------------------------------- 1 | package backend_conf 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net/url" 8 | "strings" 9 | "time" 10 | 11 | "gopkg.in/mgo.v2" 12 | "gopkg.in/mgo.v2/bson" 13 | 14 | "aproxy/module/db" 15 | ) 16 | 17 | const ( 18 | C_NAME_BackendConf = "BackendConf" 19 | ) 20 | 21 | var backendConfStorag BackendConfStorager 22 | 23 | type BackendConf struct { 24 | Id string `bson:"_id,omitempty"` 25 | Desc string `bson:"Desc"` 26 | // request hostname, 27 | // e.g: www.abc.com, 192.168.10.33 28 | HostName string `bson:"HostName"` 29 | // proxy to which host, 30 | // e.g: http://localhost:8081/ 31 | UpStreams []string `bson:"UpStreams"` 32 | // authentication type: 33 | // 0. public: every one 34 | // 1. login: login user 35 | // 2. auth: login & has permitted 36 | AuthType int `bson:"AuthType"` 37 | 38 | CreatedTime time.Time `bson:"CreatedTime"` 39 | UpdatedTime time.Time `bson:"UpdatedTime"` 40 | } 41 | 42 | type BackendConfStorager interface { 43 | Get(hostname string) (BackendConf, error) 44 | GetAll() ([]BackendConf, error) 45 | Insert(BackendConf) error 46 | Update(id string, bc BackendConf) error 47 | Delete(id string) error 48 | } 49 | 50 | // using mongodb for BackendConf Storage 51 | type MongoBackendConfStorage struct { 52 | } 53 | 54 | func (self *MongoBackendConfStorage) Get(hostname string) (BackendConf, error) { 55 | c := db.MDB().C(C_NAME_BackendConf) 56 | var bc BackendConf 57 | err := c.Find(bson.M{"HostName": hostname}).One(&bc) 58 | if err != nil { 59 | if err != mgo.ErrNotFound { 60 | log.Printf("MongoBackendConfStorage Get [%s] Error: %s", hostname, err) 61 | } 62 | return bc, err 63 | } 64 | return bc, nil 65 | } 66 | 67 | func (self *MongoBackendConfStorage) GetAll() ([]BackendConf, error) { 68 | c := db.MDB().C(C_NAME_BackendConf) 69 | bcs := []BackendConf{} 70 | err := c.Find(nil).All(&bcs) 71 | if err != nil { 72 | log.Printf("MongoBackendConfStorage GetAll Error: %s", err) 73 | return bcs, err 74 | } 75 | return bcs, nil 76 | } 77 | 78 | func (self *MongoBackendConfStorage) Insert(bc BackendConf) error { 79 | bc.Id = bson.NewObjectId().Hex() 80 | c := db.MDB().C(C_NAME_BackendConf) 81 | err := c.Insert(bc) 82 | return err 83 | } 84 | 85 | func (self *MongoBackendConfStorage) Update(id string, bc BackendConf) error { 86 | c := db.MDB().C(C_NAME_BackendConf) 87 | change := bson.M{"$set": bson.M{ 88 | "AuthType": bc.AuthType, 89 | "Desc": bc.Desc, 90 | "HostName": bc.HostName, 91 | "UpStreams": bc.UpStreams, 92 | "UpdatedTime": time.Now()}} 93 | err := c.UpdateId(id, change) 94 | return err 95 | } 96 | 97 | func (self *MongoBackendConfStorage) Delete(id string) error { 98 | c := db.MDB().C(C_NAME_BackendConf) 99 | err := c.RemoveId(id) 100 | return err 101 | } 102 | 103 | // 104 | // 105 | 106 | func SetBackendConfStorage(bcs BackendConfStorager) { 107 | if bcs == nil { 108 | panic("SetBackendConfStorage: BackendConfStorager MUST NOT (nil)!") 109 | } 110 | backendConfStorag = bcs 111 | } 112 | 113 | func SetBackendConfStorageToMongo() error { 114 | SetBackendConfStorage(&MongoBackendConfStorage{}) 115 | return nil 116 | } 117 | 118 | // 119 | // 120 | 121 | func Get(hostname string) (BackendConf, error) { 122 | hostname = strings.ToLower(hostname) 123 | return backendConfStorag.Get(hostname) 124 | } 125 | 126 | func GetAll() ([]BackendConf, error) { 127 | return backendConfStorag.GetAll() 128 | } 129 | 130 | func Insert(bc BackendConf) error { 131 | err := validBackendConf(bc) 132 | if err != nil { 133 | return err 134 | } 135 | ebc, err2 := Get(strings.ToLower(bc.HostName)) 136 | if err2 == nil && ebc.HostName == bc.HostName { 137 | return errors.New(fmt.Sprintf("host [%s] has exist.", bc.HostName)) 138 | } 139 | return backendConfStorag.Insert(bc) 140 | } 141 | 142 | func Update(id string, bc BackendConf) error { 143 | err := validBackendConf(bc) 144 | if err != nil { 145 | return err 146 | } 147 | return backendConfStorag.Update(id, bc) 148 | } 149 | 150 | func Delete(id string) error { 151 | return backendConfStorag.Delete(id) 152 | } 153 | 154 | // 155 | // 156 | 157 | func validBackendConf(bc BackendConf) error { 158 | err := validHostName(bc.HostName) 159 | if err != nil { 160 | return err 161 | } 162 | for _, upstream := range bc.UpStreams { 163 | err = validUpstream(upstream) 164 | if err != nil { 165 | return err 166 | } 167 | } 168 | return nil 169 | } 170 | 171 | func validHostName(hostname string) error { 172 | if strings.Index(hostname, "http://") == 0 || 173 | strings.Index(hostname, "https://") == 0 { 174 | return errors.New(fmt.Sprintf("hostname [%s] don't need http:// .", hostname)) 175 | } 176 | url_, err := url.Parse("http://" + hostname) 177 | if err != nil { 178 | return err 179 | } else if url_.Host == "" { 180 | return errors.New(fmt.Sprintf("hostname [%s] is wrong.", hostname)) 181 | } else if url_.Host != hostname { 182 | return errors.New(fmt.Sprintf("hostname [%s] must not contains url-path.", hostname)) 183 | } 184 | return nil 185 | } 186 | 187 | func validUpstream(upstream string) error { 188 | url_, err := url.Parse(upstream) 189 | if err != nil { 190 | return err 191 | } 192 | if strings.Index(url_.Scheme, "http") != 0 { 193 | return errors.New(fmt.Sprintf("upstream [%s] must contains http:// or https:// .", upstream)) 194 | } 195 | if url_.Host == "" { 196 | return errors.New(fmt.Sprintf("upstream [%s] has the wrong host .", upstream)) 197 | } 198 | return nil 199 | } 200 | -------------------------------------------------------------------------------- /module/constant/base.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | AUTH_TYPE_PUBLIC = 0 5 | AUTH_TYPE_LOGIN = 1 6 | AUTH_TYPE_AUTH = 2 7 | 8 | PERMISSION_STATUS_OK = 0 9 | PERMISSION_STATUS_NEED_LOGIN = 1 10 | PERMISSION_STATUS_NO_PERMISSION = 2 11 | ) 12 | 13 | const ( 14 | CTX_KEY_USER = "ctx-user" 15 | 16 | // session key 17 | SS_KEY_USER = "ss-user" 18 | ) 19 | -------------------------------------------------------------------------------- /module/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | // import ( 4 | // "errors" 5 | // "fmt" 6 | // "log" 7 | // "net/url" 8 | // "strings" 9 | // "time" 10 | 11 | // "gopkg.in/mgo.v2" 12 | // "gopkg.in/mgo.v2/bson" 13 | // ) 14 | 15 | // type DBer interface { 16 | // GetById(table, id string, val interface{}) error 17 | // GetOne(table string, query map[string]interface{}, val interface{}) error 18 | // GetAll(table string, vals interface{}) error 19 | // Update(table, id string, val interface{}) error 20 | // Insert(table string, val interface{}) error 21 | // } 22 | 23 | // type MongoDB struct { 24 | // Servers []string 25 | // Db string 26 | // dbSession *mgo.Session 27 | // } 28 | 29 | // func (self *MongoDB) GetById(table, id string, val interface{}) error { 30 | 31 | // } 32 | -------------------------------------------------------------------------------- /module/db/mongodb.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "gopkg.in/mgo.v2" 8 | ) 9 | 10 | var ( 11 | _mongoStorage *mongoStorage 12 | ) 13 | 14 | type mongoStorage struct { 15 | Servers []string 16 | Db string 17 | dbSession *mgo.Session 18 | } 19 | 20 | func newMongoStorage(servers []string, db string) (*mongoStorage, error) { 21 | storage := &mongoStorage{} 22 | storage.Servers = servers 23 | storage.Db = db 24 | session, err := mgo.Dial(strings.Join(servers, ",")) 25 | if err == nil { 26 | storage.dbSession = session 27 | go autoReconnect(session) 28 | } 29 | return storage, err 30 | } 31 | 32 | func InitMongoDB(servers []string, db string) error { 33 | var err error 34 | _mongoStorage, err = newMongoStorage(servers, db) 35 | return err 36 | } 37 | 38 | func MDB() *mgo.Database { 39 | return _mongoStorage.dbSession.DB(_mongoStorage.Db) 40 | } 41 | 42 | func autoReconnect(session *mgo.Session) { 43 | var err error 44 | for { 45 | err = session.Ping() 46 | if err != nil { 47 | // fmt.Println("Loss connection to MongoDB !!") 48 | session.Refresh() 49 | // err = session.Ping() 50 | // if err == nil { 51 | // fmt.Println("Reconnect to MongoDB successful.") 52 | // } else { 53 | // fmt.Println("Reconnect to MongoDB faild !!") 54 | // } 55 | } 56 | time.Sleep(time.Second * 10) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /module/oauth/oauth.go: -------------------------------------------------------------------------------- 1 | package oauth 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | ) 7 | 8 | var providers map[string]Oauther 9 | var providersNames []string 10 | 11 | func init() { 12 | providers = map[string]Oauther{} 13 | providersNames = []string{} 14 | } 15 | 16 | type Oauther interface { 17 | // return providers name list 18 | Providers() []string 19 | Login(providerName string, w http.ResponseWriter, r *http.Request) error 20 | Callback(providerName string, w http.ResponseWriter, r *http.Request) (email string, err error) 21 | } 22 | 23 | func Register(o Oauther) { 24 | ps := o.Providers() 25 | if ps == nil || len(ps) < 1 { 26 | return 27 | } 28 | for _, p := range ps { 29 | providers[strings.ToLower(p)] = o 30 | providersNames = append(providersNames, p) 31 | } 32 | } 33 | 34 | func GetOauther(providerName string) Oauther { 35 | providerName = strings.ToLower(providerName) 36 | return providers[providerName] 37 | } 38 | 39 | func GetProviderNameList() []string { 40 | return providersNames 41 | } 42 | -------------------------------------------------------------------------------- /module/proxy/backend.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "sync" 5 | 6 | bkconf "aproxy/module/backend_conf" 7 | "github.com/mailgun/oxy/forward" 8 | "github.com/mailgun/oxy/roundrobin" 9 | ) 10 | 11 | type Backend struct { 12 | Conf bkconf.BackendConf 13 | Fwd *forward.Forwarder `json:"-"` 14 | Lb *roundrobin.RoundRobin `json:"-"` 15 | } 16 | type Backends struct { 17 | sync.RWMutex 18 | Backends map[string]Backend 19 | } 20 | 21 | var backends Backends 22 | 23 | func init() { 24 | backends.Backends = map[string]Backend{} 25 | } 26 | -------------------------------------------------------------------------------- /module/proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | // "log" 5 | "crypto/tls" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/mailgun/oxy/forward" 10 | "github.com/mailgun/oxy/roundrobin" 11 | "github.com/mailgun/oxy/testutils" 12 | 13 | "aproxy/lib/auditlog" 14 | "aproxy/lib/rfweb" 15 | "aproxy/module/auth" 16 | "aproxy/module/auth/login" 17 | bkconf "aproxy/module/backend_conf" 18 | "aproxy/module/constant" 19 | ) 20 | 21 | func Proxy(w http.ResponseWriter, r *http.Request) { 22 | ctx := rfweb.NewContext(w, r) 23 | if b, ok := getBackend(r); ok { 24 | status := auth.CheckPermission(b.Conf.AuthType, ctx) 25 | if status == constant.PERMISSION_STATUS_OK { 26 | b.Lb.ServeHTTP(w, r) 27 | } else if status == constant.PERMISSION_STATUS_NEED_LOGIN { 28 | login.RedirectToLogin(w, r) 29 | } else if status == constant.PERMISSION_STATUS_NO_PERMISSION { 30 | http.Error(w, "no permission", http.StatusForbidden) 31 | } 32 | 33 | } else { 34 | http.NotFound(w, r) 35 | } 36 | 37 | // log 38 | u := auth.GetLoginedUser(ctx) 39 | if u == nil { 40 | u = &auth.User{ 41 | Name: "Anonymous", 42 | Email: "", 43 | } 44 | } 45 | auditlog.AccessLog(u, r.Host+r.RequestURI) 46 | 47 | } 48 | 49 | func getBackend(r *http.Request) (Backend, bool) { 50 | var b = Backend{} 51 | host := strings.ToLower(r.Host) 52 | if b, ok := backends.Backends[host]; ok { 53 | return b, true 54 | } 55 | if bc, ok := getBackendConf(host); ok { 56 | b.Conf = bc 57 | tlsConfig := &tls.Config{ 58 | InsecureSkipVerify: true, 59 | } 60 | roundTripper := &http.Transport{ 61 | TLSClientConfig: tlsConfig, 62 | } 63 | b.Fwd, _ = forward.New(forward.PassHostHeader(true), 64 | forward.WebsocketTLSClientConfig(tlsConfig), 65 | forward.RoundTripper(roundTripper)) 66 | 67 | b.Lb, _ = roundrobin.New(b.Fwd) 68 | for _, upstream := range bc.UpStreams { 69 | b.Lb.UpsertServer(testutils.ParseURI(upstream)) 70 | } 71 | backends.Lock() 72 | backends.Backends[host] = b 73 | backends.Unlock() 74 | return b, true 75 | } 76 | return b, false 77 | } 78 | 79 | func getBackendConf(hostname string) (bkconf.BackendConf, bool) { 80 | bc, err := bkconf.Get(hostname) 81 | if err == nil { 82 | return bc, true 83 | } 84 | return bc, false 85 | } 86 | 87 | func RemoveBackendConfCache() { 88 | backends.Lock() 89 | defer backends.Unlock() 90 | backends.Backends = map[string]Backend{} 91 | } 92 | -------------------------------------------------------------------------------- /module/setting/authority.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "errors" 5 | // "log" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "aproxy/lib/rfweb" 11 | "aproxy/lib/util" 12 | "aproxy/module/auth" 13 | ) 14 | 15 | type AuthorityResource struct { 16 | BaseResource 17 | } 18 | 19 | func (self *AuthorityResource) Get(ctx *rfweb.Context) { 20 | res := RespData{} 21 | id := ctx.Get("id") 22 | if id == "all" { 23 | authoritys, err := auth.GetAllAuthority() 24 | if err != nil { 25 | res.Error = err.Error() 26 | } else { 27 | res.Success = true 28 | res.Data = authoritys 29 | } 30 | } else if id != "" { 31 | authority, err := auth.GetAuthorityByID(id) 32 | if err != nil { 33 | res.Error = err.Error() 34 | } else { 35 | res.Success = true 36 | res.Data = authority 37 | } 38 | } else { 39 | email := ctx.Get("email") 40 | if email != "" { 41 | authority, err := auth.GetAuthorityByEmail(email) 42 | if err != nil { 43 | res.Error = err.Error() 44 | } else { 45 | res.Success = true 46 | res.Data = authority 47 | } 48 | } 49 | } 50 | 51 | util.WriteJson(ctx.W, res) 52 | } 53 | 54 | // add new authority 55 | func (self *AuthorityResource) Post(ctx *rfweb.Context) { 56 | res := RespData{} 57 | authority, err := getAuthorityFromBody(ctx.R) 58 | if err != nil { 59 | res.Error = err.Error() 60 | } else { 61 | err = auth.InsertAuthority(authority) 62 | if err != nil { 63 | res.Error = err.Error() 64 | } else { 65 | res.Data = authority 66 | res.Success = true 67 | // proxy.RemoveBackendConfCache() 68 | } 69 | } 70 | util.WriteJson(ctx.W, res) 71 | } 72 | 73 | // update authority 74 | func (self *AuthorityResource) Put(ctx *rfweb.Context) { 75 | res := RespData{} 76 | authority, err := getAuthorityFromBody(ctx.R) 77 | if err != nil { 78 | res.Error = err.Error() 79 | } else { 80 | err = auth.UpdateAuthority(authority.Id, authority) 81 | if err != nil { 82 | res.Error = err.Error() 83 | } else { 84 | res.Data = authority 85 | res.Success = true 86 | // proxy.RemoveBackendConfCache() 87 | } 88 | } 89 | util.WriteJson(ctx.W, res) 90 | } 91 | 92 | // delete authority 93 | func (self *AuthorityResource) Delete(ctx *rfweb.Context) { 94 | res := RespData{} 95 | id := ctx.Get("id") 96 | if len(id) < 1 { 97 | res.Error = "no id" 98 | } else { 99 | err := auth.DeleteAuthority(id) 100 | if err == nil { 101 | res.Success = true 102 | res.Data = id 103 | } else { 104 | res.Error = err.Error() 105 | } 106 | } 107 | util.WriteJson(ctx.W, res) 108 | } 109 | 110 | func getAuthorityFromBody(r *http.Request) (*auth.Authority, error) { 111 | authority := &auth.Authority{} 112 | err := util.DecodeJsonBody(r.Body, &authority) 113 | if err != nil { 114 | return authority, err 115 | } 116 | 117 | authority.Email = strings.TrimSpace(authority.Email) 118 | if len(authority.Email) < 1 { 119 | return authority, errors.New("[Email] must not empty.") 120 | } 121 | 122 | allow := []string{} 123 | for _, a := range authority.Allow { 124 | a = strings.TrimSpace(a) 125 | if a != "" { 126 | allow = append(allow, a) 127 | } 128 | } 129 | if len(allow) > 0 { 130 | authority.Allow = allow 131 | } 132 | deny := []string{} 133 | for _, d := range authority.Deny { 134 | d = strings.TrimSpace(d) 135 | if d != "" { 136 | deny = append(deny, d) 137 | } 138 | } 139 | if len(deny) > 0 { 140 | authority.Deny = deny 141 | } 142 | if len(deny) < 1 && len(allow) < 1 && len(authority.Roles) < 1 { 143 | return authority, errors.New("[Deny], [Allow] and [Roles] can't all be empty.") 144 | } 145 | 146 | authority.CreatedTime = time.Now() 147 | authority.UpdatedTime = authority.CreatedTime 148 | return authority, nil 149 | } 150 | -------------------------------------------------------------------------------- /module/setting/backend_conf.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "errors" 5 | // "log" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "aproxy/lib/rfweb" 11 | "aproxy/lib/util" 12 | bkconf "aproxy/module/backend_conf" 13 | "aproxy/module/proxy" 14 | ) 15 | 16 | type BackendConfResource struct { 17 | BaseResource 18 | } 19 | 20 | func (self *BackendConfResource) Get(ctx *rfweb.Context) { 21 | res := RespData{} 22 | hostname := ctx.Get("hostname") 23 | if hostname == "all" { 24 | bcs, err := bkconf.GetAll() 25 | if err != nil { 26 | res.Error = err.Error() 27 | } else { 28 | res.Success = true 29 | res.Data = bcs 30 | } 31 | } else { 32 | bc, err := bkconf.Get(hostname) 33 | if err != nil { 34 | res.Error = err.Error() 35 | } else { 36 | res.Success = true 37 | res.Data = bc 38 | } 39 | } 40 | 41 | util.WriteJson(ctx.W, res) 42 | } 43 | 44 | // add new backend config 45 | func (self *BackendConfResource) Post(ctx *rfweb.Context) { 46 | res := RespData{} 47 | bc, err := getBackendConfFromBody(ctx.R) 48 | if err != nil { 49 | res.Error = err.Error() 50 | } else { 51 | err = bkconf.Insert(bc) 52 | if err != nil { 53 | res.Error = err.Error() 54 | } else { 55 | res.Data = bc 56 | res.Success = true 57 | proxy.RemoveBackendConfCache() 58 | } 59 | } 60 | util.WriteJson(ctx.W, res) 61 | } 62 | 63 | // update backend config 64 | func (self *BackendConfResource) Put(ctx *rfweb.Context) { 65 | res := RespData{} 66 | bc, err := getBackendConfFromBody(ctx.R) 67 | if err != nil { 68 | res.Error = err.Error() 69 | } else { 70 | err = bkconf.Update(bc.Id, bc) 71 | if err != nil { 72 | res.Error = err.Error() 73 | } else { 74 | res.Data = bc 75 | res.Success = true 76 | proxy.RemoveBackendConfCache() 77 | } 78 | } 79 | util.WriteJson(ctx.W, res) 80 | } 81 | 82 | // delete role 83 | func (self *BackendConfResource) Delete(ctx *rfweb.Context) { 84 | res := RespData{} 85 | id := ctx.Get("id") 86 | if len(id) < 1 { 87 | res.Error = "no id" 88 | } else { 89 | err := bkconf.Delete(id) 90 | if err == nil { 91 | res.Success = true 92 | res.Data = id 93 | } else { 94 | res.Error = err.Error() 95 | } 96 | } 97 | util.WriteJson(ctx.W, res) 98 | } 99 | 100 | func getBackendConfFromBody(r *http.Request) (bkconf.BackendConf, error) { 101 | bc := bkconf.BackendConf{} 102 | err := util.DecodeJsonBody(r.Body, &bc) 103 | if err != nil { 104 | return bc, err 105 | } 106 | bc.HostName = strings.ToLower(strings.TrimSpace(bc.HostName)) 107 | if bc.HostName == "" { 108 | return bc, errors.New("[hostname] must not empty.") 109 | } 110 | upstreams := []string{} 111 | for _, upstream := range bc.UpStreams { 112 | upstream = strings.TrimSpace(upstream) 113 | if upstream != "" { 114 | upstreams = append(upstreams, upstream) 115 | } 116 | } 117 | if len(upstreams) > 0 { 118 | bc.UpStreams = upstreams 119 | } else { 120 | return bc, errors.New("[upstreams] must not empty.") 121 | } 122 | bc.CreatedTime = time.Now() 123 | bc.UpdatedTime = bc.CreatedTime 124 | return bc, nil 125 | } 126 | -------------------------------------------------------------------------------- /module/setting/base.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "net/http" 5 | 6 | "aproxy/lib/rfweb" 7 | "aproxy/lib/util" 8 | "aproxy/module/auth" 9 | "aproxy/module/auth/login" 10 | ) 11 | 12 | var ( 13 | fileServer http.Handler 14 | 15 | inited = false 16 | staticFileDir = "./" 17 | AproxyUrlPrefix = "/-_-aproxy-_-/" 18 | ) 19 | 20 | type RespData struct { 21 | Success bool `json:"success"` 22 | Error string `json:"error"` 23 | Data interface{} `json:"data"` 24 | } 25 | 26 | type BaseResource struct { 27 | rfweb.BaseResource 28 | } 29 | 30 | // check permission 31 | func (self *BaseResource) OnHandleBegin(ctx *rfweb.Context) bool { 32 | user := auth.GetLoginedUser(ctx) 33 | errMsg := "" 34 | if user == nil || user.Email == "" { 35 | errMsg = "please login first." 36 | } else { 37 | authority, err := auth.GetAuthorityByEmail(user.Email) 38 | if err != nil { 39 | errMsg = "can't get authority, error: " + err.Error() 40 | } else if authority == nil || authority.AdminLevel < 10 { 41 | errMsg = "you don't has permission." 42 | } 43 | } 44 | if errMsg != "" { 45 | isXHR := ctx.R.Header.Get("X-Requested-With") == "XMLHttpRequest" 46 | if isXHR { 47 | res := RespData{ 48 | Error: errMsg, 49 | } 50 | util.WriteJson(ctx.W, res) 51 | } else { 52 | http.Error(ctx.W, errMsg, http.StatusForbidden) 53 | } 54 | return false 55 | } 56 | return true 57 | } 58 | 59 | func (self *BaseResource) OnHandleEnd(ctx *rfweb.Context) { 60 | 61 | } 62 | 63 | func InitSettingServer(webDir, aproxyUrlPrefix string) { 64 | inited = true 65 | staticFileDir = webDir 66 | if aproxyUrlPrefix != "" { 67 | AproxyUrlPrefix = aproxyUrlPrefix 68 | } 69 | fileServer = http.FileServer(http.Dir(staticFileDir)) 70 | } 71 | 72 | func StaticServer(w http.ResponseWriter, r *http.Request) { 73 | // check permission 74 | if r.RequestURI == AproxyUrlPrefix || 75 | r.RequestURI == AproxyUrlPrefix+"index.html" { 76 | ctx := rfweb.NewContext(w, r) 77 | user := auth.GetLoginedUser(ctx) 78 | errMsg := "" 79 | if user == nil { 80 | login.RedirectToLogin(w, r) 81 | return 82 | } else { 83 | authority, err := auth.GetAuthorityByEmail(user.Email) 84 | if err != nil { 85 | errMsg = "can't get authority, error: " + err.Error() 86 | } else if authority == nil || authority.AdminLevel < 10 { 87 | errMsg = "you don't has permission." 88 | } 89 | } 90 | if errMsg != "" { 91 | http.Error(ctx.W, errMsg, http.StatusForbidden) 92 | return 93 | } 94 | } 95 | 96 | http.StripPrefix(AproxyUrlPrefix, 97 | fileServer).ServeHTTP(w, r) 98 | } 99 | 100 | func NewApiApp() *rfweb.App { 101 | app := rfweb.NewApp(AproxyUrlPrefix + "api/") 102 | app.Resource("backends/{hostname}", &BackendConfResource{}) 103 | app.Resource("role/{id}", &RoleResource{}) 104 | app.Resource("authority/{id}", &AuthorityResource{}) 105 | 106 | app.Resource("users/{email}", &UserResource{}) 107 | app.Resource("user/login", &login.LoginResource{}) 108 | app.Resource("user/logout", &login.LogoutResource{}) 109 | 110 | app.Resource("oauth/list", &login.OauthListResource{}) 111 | app.Resource("oauth/login", &login.OauthLoginResource{}) 112 | app.Resource("oauth/callback", &login.OauthCallbackResource{}) 113 | 114 | return app 115 | } 116 | -------------------------------------------------------------------------------- /module/setting/role.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "errors" 5 | // "log" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "aproxy/lib/rfweb" 11 | "aproxy/lib/util" 12 | "aproxy/module/auth" 13 | ) 14 | 15 | type RoleResource struct { 16 | BaseResource 17 | } 18 | 19 | func (self *RoleResource) Get(ctx *rfweb.Context) { 20 | res := RespData{} 21 | id := ctx.Get("id") 22 | if id == "all" { 23 | roles, err := auth.GetAllRole() 24 | if err != nil { 25 | res.Error = err.Error() 26 | } else { 27 | res.Success = true 28 | res.Data = roles 29 | } 30 | } else { 31 | role, err := auth.GetRoleByID(id) 32 | if err != nil { 33 | res.Error = err.Error() 34 | } else { 35 | res.Success = true 36 | res.Data = role 37 | } 38 | } 39 | 40 | util.WriteJson(ctx.W, res) 41 | } 42 | 43 | // add new role 44 | func (self *RoleResource) Post(ctx *rfweb.Context) { 45 | res := RespData{} 46 | role, err := getRoleFromBody(ctx.R) 47 | if err != nil { 48 | res.Error = err.Error() 49 | } else { 50 | err = auth.InsertRole(role) 51 | if err != nil { 52 | res.Error = err.Error() 53 | } else { 54 | res.Data = role 55 | res.Success = true 56 | // proxy.RemoveBackendConfCache() 57 | } 58 | } 59 | util.WriteJson(ctx.W, res) 60 | } 61 | 62 | // update role config 63 | func (self *RoleResource) Put(ctx *rfweb.Context) { 64 | res := RespData{} 65 | role, err := getRoleFromBody(ctx.R) 66 | if err != nil { 67 | res.Error = err.Error() 68 | } else { 69 | err = auth.UpdateRole(role.Id, role) 70 | if err != nil { 71 | res.Error = err.Error() 72 | } else { 73 | res.Data = role 74 | res.Success = true 75 | // proxy.RemoveBackendConfCache() 76 | } 77 | } 78 | util.WriteJson(ctx.W, res) 79 | } 80 | 81 | // delete role 82 | func (self *RoleResource) Delete(ctx *rfweb.Context) { 83 | res := RespData{} 84 | id := ctx.Get("id") 85 | if len(id) < 1 { 86 | res.Error = "no id" 87 | } else { 88 | err := auth.DeleteRole(id) 89 | if err == nil { 90 | res.Success = true 91 | res.Data = id 92 | } else { 93 | res.Error = err.Error() 94 | } 95 | } 96 | util.WriteJson(ctx.W, res) 97 | } 98 | 99 | func getRoleFromBody(r *http.Request) (*auth.Role, error) { 100 | role := &auth.Role{} 101 | err := util.DecodeJsonBody(r.Body, &role) 102 | if err != nil { 103 | return role, err 104 | } 105 | 106 | role.Name = strings.TrimSpace(role.Name) 107 | if len(role.Name) < 1 { 108 | return role, errors.New("[Name] must not empty.") 109 | } 110 | 111 | allow := []string{} 112 | for _, a := range role.Allow { 113 | a = strings.TrimSpace(a) 114 | if a != "" { 115 | allow = append(allow, a) 116 | } 117 | } 118 | if len(allow) > 0 { 119 | role.Allow = allow 120 | } 121 | deny := []string{} 122 | for _, d := range role.Deny { 123 | d = strings.TrimSpace(d) 124 | if d != "" { 125 | deny = append(deny, d) 126 | } 127 | } 128 | if len(deny) > 0 { 129 | role.Deny = deny 130 | } 131 | if len(deny) < 1 && len(allow) < 1 { 132 | return role, errors.New("[Deny] and [Allow] can't all be empty.") 133 | } 134 | 135 | role.CreatedTime = time.Now() 136 | role.UpdatedTime = role.CreatedTime 137 | return role, nil 138 | } 139 | -------------------------------------------------------------------------------- /module/setting/user.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "errors" 5 | // "log" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "aproxy/lib/rfweb" 11 | "aproxy/lib/util" 12 | "aproxy/module/auth" 13 | ) 14 | 15 | // with auth.User, 16 | // need marshal exclude `Pwd` field, 17 | // but unmarshal include `Pwd` field. 18 | // but the json pkg can't do that, 19 | // so create this struct 20 | type FormUser struct { 21 | Id string 22 | Email string 23 | Pwd string 24 | } 25 | 26 | type UserResource struct { 27 | BaseResource 28 | } 29 | 30 | func (self *UserResource) Get(ctx *rfweb.Context) { 31 | res := RespData{} 32 | email := ctx.Get("email") 33 | if email == "all" { 34 | users, err := auth.GetAllUsers() 35 | if err != nil { 36 | res.Error = err.Error() 37 | } else { 38 | res.Success = true 39 | res.Data = users 40 | } 41 | } else { 42 | user, err := auth.GetUserByEmail(email) 43 | if err != nil { 44 | res.Error = err.Error() 45 | } else { 46 | res.Success = true 47 | res.Data = user 48 | } 49 | } 50 | 51 | util.WriteJson(ctx.W, res) 52 | } 53 | 54 | // add new authority 55 | func (self *UserResource) Post(ctx *rfweb.Context) { 56 | res := RespData{} 57 | user, err := getUserFromBody(ctx.R, true) 58 | if err != nil { 59 | res.Error = err.Error() 60 | } else { 61 | err = auth.InsertUser(*user) 62 | if err != nil { 63 | res.Error = err.Error() 64 | } else { 65 | res.Data = user 66 | res.Success = true 67 | // proxy.RemoveBackendConfCache() 68 | } 69 | } 70 | util.WriteJson(ctx.W, res) 71 | } 72 | 73 | // update user 74 | func (self *UserResource) Put(ctx *rfweb.Context) { 75 | res := RespData{} 76 | user, err := getUserFromBody(ctx.R, false) 77 | if err != nil { 78 | res.Error = err.Error() 79 | } else { 80 | err = auth.UpdateUser(user.Id, *user) 81 | if err != nil { 82 | res.Error = err.Error() 83 | } else { 84 | res.Data = user 85 | res.Success = true 86 | // proxy.RemoveBackendConfCache() 87 | } 88 | } 89 | util.WriteJson(ctx.W, res) 90 | } 91 | 92 | // delete user 93 | func (self *UserResource) Delete(ctx *rfweb.Context) { 94 | res := RespData{} 95 | id := ctx.Get("id") 96 | if len(id) < 1 { 97 | res.Error = "no id" 98 | } else { 99 | err := auth.DeleteUser(id) 100 | if err == nil { 101 | res.Success = true 102 | res.Data = id 103 | } else { 104 | res.Error = err.Error() 105 | } 106 | } 107 | util.WriteJson(ctx.W, res) 108 | } 109 | 110 | func getUserFromBody(r *http.Request, needPwd bool) (*auth.User, error) { 111 | fuser := FormUser{} 112 | user := &auth.User{} 113 | err := util.DecodeJsonBody(r.Body, &fuser) 114 | if err != nil { 115 | return user, err 116 | } 117 | user.Id = fuser.Id 118 | user.Email = fuser.Email 119 | user.Pwd = fuser.Pwd 120 | 121 | user.Email = strings.TrimSpace(user.Email) 122 | if len(user.Email) < 1 { 123 | return user, errors.New("[Email] must not empty.") 124 | } 125 | user.Pwd = strings.TrimSpace(user.Pwd) 126 | if needPwd && len(user.Pwd) < 1 { 127 | return user, errors.New("[Password] must not empty.") 128 | } 129 | 130 | user.CreatedTime = time.Now() 131 | user.UpdatedTime = user.CreatedTime 132 | return user, nil 133 | } 134 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | APROXY_VER="0.3" 4 | APROXY_BIN_GO="./bin/aproxy.go" 5 | 6 | if [ ! -f "$APROXY_BIN_GO" ]; then 7 | echo "please enter aproxy root dir." 8 | exit 1 9 | fi 10 | 11 | eval $(go env) 12 | 13 | GIT_SHA=`git rev-parse --short HEAD || echo "GitNotFound"` 14 | val=$(go version) 15 | gover=$(echo $val | awk -F ' ' '{print $3}') 16 | 17 | outdir="aproxy-v$APROXY_VER-$gover-git$GIT_SHA" 18 | 19 | all=('linux 386' 'linux amd64' 'darwin 386' 'darwin amd64' 'windows 386' 'windows amd64') 20 | 21 | for i in "${all[@]}" ; do 22 | b=($i) 23 | os=${b[0]} 24 | bit=${b[1]} 25 | outdir="aproxy-v$APROXY_VER-$os-$bit-$gover-git$GIT_SHA" 26 | echo "start build [$os-$bit] to ./release/$outdir" 27 | rm -rf ./release/$outdir/* 28 | mkdir -p ./release/$outdir/bin 29 | mkdir -p ./release/$outdir/conf 30 | 31 | if [ "$os"x = "windows"x ]; then 32 | GOOS=$os GOARCH=$bit go build -o ./release/$outdir/bin/aproxy.exe ./bin/aproxy.go 33 | GOOS=$os GOARCH=$bit go build -o ./release/$outdir/bin/adduser.exe ./bin/adduser.go 34 | else 35 | GOOS=$os GOARCH=$bit go build -o ./release/$outdir/bin/aproxy ./bin/aproxy.go 36 | GOOS=$os GOARCH=$bit go build -o ./release/$outdir/bin/adduser ./bin/adduser.go 37 | fi 38 | 39 | yes|cp -rf ./web ./release/$outdir/ 40 | yes|cp -f ./conf/aproxy.toml ./release/$outdir/conf/aproxy.toml.example 41 | done 42 | -------------------------------------------------------------------------------- /web/static/css/base.css: -------------------------------------------------------------------------------- 1 | body{padding-top:80px} 2 | #oauthProviderList a { 3 | margin-right: 3px; 4 | } -------------------------------------------------------------------------------- /web/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | aProxy Setting 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 89 | 90 |
91 |
92 |
93 | 94 | 95 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /web/static/js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* App Module */ 4 | 5 | var aproxyApp = angular.module('aproxyApp', [ 6 | 'ngRoute', 7 | 'aproxyControllers', 8 | 'aproxyFilters', 9 | 'aproxyServices' 10 | ]); 11 | 12 | aproxyApp.config(['$httpProvider', function($httpProvider) { 13 | $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'; 14 | }]); 15 | 16 | aproxyApp.config(['$routeProvider', 17 | function($routeProvider) { 18 | $routeProvider. 19 | when('/backends', { 20 | templateUrl: 'partials/backend-conf/list.html', 21 | controller: 'BackendConfListCtrl' 22 | }). 23 | when('/backend-conf/new', { 24 | templateUrl: 'partials/backend-conf/new.html', 25 | controller: 'BackendConfAddNewCtrl' 26 | }). 27 | when('/backend-conf/:hostname', { 28 | templateUrl: 'partials/backend-conf/detail.html', 29 | controller: 'BackendConfDetailCtrl' 30 | }). 31 | otherwise({ 32 | redirectTo: '/backends' 33 | }); 34 | 35 | $routeProvider. 36 | when('/role', { 37 | templateUrl: 'partials/role/list.html', 38 | controller: 'RoleListCtrl' 39 | }). 40 | when('/role/new', { 41 | templateUrl: 'partials/role/new.html', 42 | controller: 'RoleAddNewCtrl' 43 | }). 44 | when('/role/:id', { 45 | templateUrl: 'partials/role/detail.html', 46 | controller: 'RoleDetailCtrl' 47 | }); 48 | 49 | $routeProvider. 50 | when('/authority', { 51 | templateUrl: 'partials/authority/list.html', 52 | controller: 'AuthorityListCtrl' 53 | }). 54 | when('/authority/new', { 55 | templateUrl: 'partials/authority/new.html', 56 | controller: 'AuthorityAddNewCtrl' 57 | }). 58 | when('/authority/:id', { 59 | templateUrl: 'partials/authority/detail.html', 60 | controller: 'AuthorityDetailCtrl' 61 | }); 62 | 63 | $routeProvider. 64 | when('/users', { 65 | templateUrl: 'partials/users/list.html', 66 | controller: 'UserListCtrl' 67 | }). 68 | when('/users/new', { 69 | templateUrl: 'partials/users/new.html', 70 | controller: 'UserAddNewCtrl' 71 | }). 72 | when('/users/:email', { 73 | templateUrl: 'partials/users/detail.html', 74 | controller: 'UserDetailCtrl' 75 | }); 76 | 77 | }]); 78 | -------------------------------------------------------------------------------- /web/static/js/directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Directives */ 4 | -------------------------------------------------------------------------------- /web/static/js/filters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Filters */ 4 | 5 | angular.module('aproxyFilters', []).filter('checkmark', function() { 6 | return function(input) { 7 | return input ? '\u2713' : '\u2718'; 8 | }; 9 | }); 10 | -------------------------------------------------------------------------------- /web/static/js/login.js: -------------------------------------------------------------------------------- 1 | function login () { 2 | var email = $('#inputEmail').val(); 3 | var pwd = $('#inputPassword').val(); 4 | var rememberMe = $('#inputRemember').is(':checked'); 5 | if (!email) { alert('Please enter the Email.'); return false; } 6 | if (!pwd) { alert('Please enter the Password.'); return false; } 7 | $('#login-form :input').attr('disabled', true); 8 | $('#login-form .progress').show(); 9 | 10 | $.ajax({ 11 | type: 'POST', 12 | dataType: 'json', 13 | url: 'api/user/login', 14 | data: {'email':email, 'pwd':pwd, 'remember':rememberMe ? 1:0}, 15 | success: function (res, textStatus, jqXHR) { 16 | if (res.success) { 17 | var tourl = getURLParameterByName('returnurl'); 18 | tourl = tourl || "/"; 19 | window.location = tourl; 20 | } else { 21 | alert('login faild: ' + res.error); 22 | } 23 | }, 24 | complete: function () { 25 | $('#login-form :input').attr('disabled', false); 26 | $('#login-form .progress').hide(); 27 | } 28 | 29 | }); 30 | return false; 31 | } 32 | 33 | function getURLParameterByName(name) { 34 | name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 35 | var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 36 | results = regex.exec(location.search); 37 | return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 38 | } 39 | 40 | $(document).ready(function(){ 41 | $.get("api/oauth/list",function(data, status){ 42 | console.dir("Data: " + data + "\nStatus: " + status); 43 | if (data.data && data.data.length > 0) { 44 | $("#oauthLogin").show(); 45 | var tourl = getURLParameterByName('returnurl'); 46 | var oauthProviders = ""; 47 | for (var i = 0; i < data.data.length; i++) { 48 | var provider = data.data[i]; 49 | oauthProviders += ''+provider+''; 50 | } 51 | $("#oauthProviderList").html(oauthProviders) 52 | } 53 | }); 54 | }); 55 | 56 | -------------------------------------------------------------------------------- /web/static/js/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Services */ 4 | 5 | var aproxyServices = angular.module('aproxyServices', ['ngResource']); 6 | 7 | aproxyServices.factory('BackendConf', ['$resource', 8 | function($resource){ 9 | return $resource('api/backends/:hostname', {}, { 10 | query: {method:'GET', params:{hostname:'all'}}, 11 | remove: {method:'DELETE'}, 12 | update: {method:'PUT'} 13 | }); 14 | }]); 15 | 16 | aproxyServices.factory('Role', ['$resource', 17 | function($resource){ 18 | return $resource('api/role/:id', {}, { 19 | query: {method:'GET', params:{id:'all'}}, 20 | remove: {method:'DELETE'}, 21 | update: {method:'PUT'} 22 | }); 23 | }]); 24 | 25 | 26 | aproxyServices.factory('Authority', ['$resource', 27 | function($resource){ 28 | return $resource('api/authority/:id', {}, { 29 | query: {method:'GET', params:{id:'all'}}, 30 | remove: {method:'DELETE'}, 31 | update: {method:'PUT'} 32 | }); 33 | }]); 34 | 35 | aproxyServices.factory('User', ['$resource', 36 | function($resource){ 37 | return $resource('api/users/:email', {}, { 38 | query: {method:'GET', params:{email:'all'}}, 39 | remove: {method:'DELETE'}, 40 | update: {method:'PUT'} 41 | }); 42 | }]); -------------------------------------------------------------------------------- /web/static/lib/angular-animate/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-animate", 3 | "version": "1.4.4", 4 | "main": "./angular-animate.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "angular": "1.4.4" 8 | }, 9 | "homepage": "https://github.com/angular/bower-angular-animate", 10 | "_release": "1.4.4", 11 | "_resolution": { 12 | "type": "version", 13 | "tag": "v1.4.4", 14 | "commit": "0a261311373ca83245a2cd76a459ccd06c9a8cea" 15 | }, 16 | "_source": "git://github.com/angular/bower-angular-animate.git", 17 | "_target": "1.4.x", 18 | "_originalSource": "angular-animate" 19 | } -------------------------------------------------------------------------------- /web/static/lib/angular-animate/README.md: -------------------------------------------------------------------------------- 1 | # packaged angular-animate 2 | 3 | This repo is for distribution on `npm` and `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngAnimate). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | You can install this package either with `npm` or with `bower`. 10 | 11 | ### npm 12 | 13 | ```shell 14 | npm install angular-animate 15 | ``` 16 | 17 | Then add `ngAnimate` as a dependency for your app: 18 | 19 | ```javascript 20 | angular.module('myApp', [require('angular-animate')]); 21 | ``` 22 | 23 | ### bower 24 | 25 | ```shell 26 | bower install angular-animate 27 | ``` 28 | 29 | Then add a ` 33 | ``` 34 | 35 | Then add `ngAnimate` as a dependency for your app: 36 | 37 | ```javascript 38 | angular.module('myApp', ['ngAnimate']); 39 | ``` 40 | 41 | ## Documentation 42 | 43 | Documentation is available on the 44 | [AngularJS docs site](http://docs.angularjs.org/api/ngAnimate). 45 | 46 | ## License 47 | 48 | The MIT License 49 | 50 | Copyright (c) 2010-2015 Google, Inc. http://angularjs.org 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy 53 | of this software and associated documentation files (the "Software"), to deal 54 | in the Software without restriction, including without limitation the rights 55 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 56 | copies of the Software, and to permit persons to whom the Software is 57 | furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in 60 | all copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 63 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 64 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 65 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 66 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 67 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 68 | THE SOFTWARE. 69 | -------------------------------------------------------------------------------- /web/static/lib/angular-animate/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-animate", 3 | "version": "1.4.4", 4 | "main": "./angular-animate.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "angular": "1.4.4" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /web/static/lib/angular-animate/index.js: -------------------------------------------------------------------------------- 1 | require('./angular-animate'); 2 | module.exports = 'ngAnimate'; 3 | -------------------------------------------------------------------------------- /web/static/lib/angular-animate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-animate", 3 | "version": "1.4.4", 4 | "description": "AngularJS module for animations", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/angular/angular.js.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "framework", 16 | "browser", 17 | "animation", 18 | "client-side" 19 | ], 20 | "author": "Angular Core Team ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/angular/angular.js/issues" 24 | }, 25 | "homepage": "http://angularjs.org" 26 | } 27 | -------------------------------------------------------------------------------- /web/static/lib/angular-mocks/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-mocks", 3 | "version": "1.4.4", 4 | "main": "./angular-mocks.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "angular": "1.4.4" 8 | }, 9 | "homepage": "https://github.com/angular/bower-angular-mocks", 10 | "_release": "1.4.4", 11 | "_resolution": { 12 | "type": "version", 13 | "tag": "v1.4.4", 14 | "commit": "38253ca16b9edec7a16194cc68f1ffe45768f21d" 15 | }, 16 | "_source": "git://github.com/angular/bower-angular-mocks.git", 17 | "_target": "1.4.x", 18 | "_originalSource": "angular-mocks" 19 | } -------------------------------------------------------------------------------- /web/static/lib/angular-mocks/README.md: -------------------------------------------------------------------------------- 1 | # packaged angular-mocks 2 | 3 | This repo is for distribution on `npm` and `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngMock). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | You can install this package either with `npm` or with `bower`. 10 | 11 | ### npm 12 | 13 | ```shell 14 | npm install angular-mocks 15 | ``` 16 | 17 | You can `require` ngMock modules: 18 | 19 | ```js 20 | var angular = require('angular'); 21 | angular.module('myMod', [ 22 | require('angular-animate'), 23 | require('angular-mocks/ngMock') 24 | require('angular-mocks/ngAnimateMock') 25 | ]); 26 | ``` 27 | 28 | ### bower 29 | 30 | ```shell 31 | bower install angular-mocks 32 | ``` 33 | 34 | The mocks are then available at `bower_components/angular-mocks/angular-mocks.js`. 35 | 36 | ## Documentation 37 | 38 | Documentation is available on the 39 | [AngularJS docs site](https://docs.angularjs.org/guide/unit-testing). 40 | 41 | ## License 42 | 43 | The MIT License 44 | 45 | Copyright (c) 2010-2015 Google, Inc. http://angularjs.org 46 | 47 | Permission is hereby granted, free of charge, to any person obtaining a copy 48 | of this software and associated documentation files (the "Software"), to deal 49 | in the Software without restriction, including without limitation the rights 50 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 51 | copies of the Software, and to permit persons to whom the Software is 52 | furnished to do so, subject to the following conditions: 53 | 54 | The above copyright notice and this permission notice shall be included in 55 | all copies or substantial portions of the Software. 56 | 57 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 58 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 59 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 60 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 61 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 62 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 63 | THE SOFTWARE. 64 | -------------------------------------------------------------------------------- /web/static/lib/angular-mocks/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-mocks", 3 | "version": "1.4.4", 4 | "main": "./angular-mocks.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "angular": "1.4.4" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /web/static/lib/angular-mocks/ngAnimateMock.js: -------------------------------------------------------------------------------- 1 | require('./angular-mocks'); 2 | module.exports = 'ngAnimateMock'; 3 | -------------------------------------------------------------------------------- /web/static/lib/angular-mocks/ngMock.js: -------------------------------------------------------------------------------- 1 | require('./angular-mocks'); 2 | module.exports = 'ngMock'; 3 | -------------------------------------------------------------------------------- /web/static/lib/angular-mocks/ngMockE2E.js: -------------------------------------------------------------------------------- 1 | require('./angular-mocks'); 2 | module.exports = 'ngMockE2E'; 3 | -------------------------------------------------------------------------------- /web/static/lib/angular-mocks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-mocks", 3 | "version": "1.4.4", 4 | "description": "AngularJS mocks for testing", 5 | "main": "angular-mocks.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/angular/angular.js.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "framework", 16 | "browser", 17 | "mocks", 18 | "testing", 19 | "client-side" 20 | ], 21 | "author": "Angular Core Team ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/angular/angular.js/issues" 25 | }, 26 | "homepage": "http://angularjs.org" 27 | } 28 | -------------------------------------------------------------------------------- /web/static/lib/angular-module/checklist-model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checklist-model 3 | * AngularJS directive for list of checkboxes 4 | * https://github.com/vitalets/checklist-model 5 | * License: MIT http://opensource.org/licenses/MIT 6 | */ 7 | 8 | angular.module('checklist-model', []) 9 | .directive('checklistModel', ['$parse', '$compile', function($parse, $compile) { 10 | // contains 11 | function contains(arr, item, comparator) { 12 | if (angular.isArray(arr)) { 13 | for (var i = arr.length; i--;) { 14 | if (comparator(arr[i], item)) { 15 | return true; 16 | } 17 | } 18 | } 19 | return false; 20 | } 21 | 22 | // add 23 | function add(arr, item, comparator) { 24 | arr = angular.isArray(arr) ? arr : []; 25 | if(!contains(arr, item, comparator)) { 26 | arr.push(item); 27 | } 28 | return arr; 29 | } 30 | 31 | // remove 32 | function remove(arr, item, comparator) { 33 | if (angular.isArray(arr)) { 34 | for (var i = arr.length; i--;) { 35 | if (comparator(arr[i], item)) { 36 | arr.splice(i, 1); 37 | break; 38 | } 39 | } 40 | } 41 | return arr; 42 | } 43 | 44 | // http://stackoverflow.com/a/19228302/1458162 45 | function postLinkFn(scope, elem, attrs) { 46 | // exclude recursion, but still keep the model 47 | var checklistModel = attrs.checklistModel; 48 | attrs.$set("checklistModel", null); 49 | // compile with `ng-model` pointing to `checked` 50 | $compile(elem)(scope); 51 | attrs.$set("checklistModel", checklistModel); 52 | 53 | // getter / setter for original model 54 | var getter = $parse(checklistModel); 55 | var setter = getter.assign; 56 | var checklistChange = $parse(attrs.checklistChange); 57 | 58 | // value added to list 59 | var value = attrs.checklistValue ? $parse(attrs.checklistValue)(scope.$parent) : attrs.value; 60 | 61 | 62 | var comparator = angular.equals; 63 | 64 | if (attrs.hasOwnProperty('checklistComparator')){ 65 | if (attrs.checklistComparator[0] == '.') { 66 | var comparatorExpression = attrs.checklistComparator.substring(1); 67 | comparator = function (a, b) { 68 | return a[comparatorExpression] === b[comparatorExpression]; 69 | } 70 | 71 | } else { 72 | comparator = $parse(attrs.checklistComparator)(scope.$parent); 73 | } 74 | } 75 | 76 | // watch UI checked change 77 | scope.$watch(attrs.ngModel, function(newValue, oldValue) { 78 | if (newValue === oldValue) { 79 | return; 80 | } 81 | var current = getter(scope.$parent); 82 | if (angular.isFunction(setter)) { 83 | if (newValue === true) { 84 | setter(scope.$parent, add(current, value, comparator)); 85 | } else { 86 | setter(scope.$parent, remove(current, value, comparator)); 87 | } 88 | } 89 | 90 | if (checklistChange) { 91 | checklistChange(scope); 92 | } 93 | }); 94 | 95 | // declare one function to be used for both $watch functions 96 | function setChecked(newArr, oldArr) { 97 | scope[attrs.ngModel] = contains(newArr, value, comparator); 98 | } 99 | 100 | // watch original model change 101 | // use the faster $watchCollection method if it's available 102 | if (angular.isFunction(scope.$parent.$watchCollection)) { 103 | scope.$parent.$watchCollection(checklistModel, setChecked); 104 | } else { 105 | scope.$parent.$watch(checklistModel, setChecked, true); 106 | } 107 | } 108 | 109 | return { 110 | restrict: 'A', 111 | priority: 1000, 112 | terminal: true, 113 | scope: true, 114 | compile: function(tElement, tAttrs) { 115 | if ((tElement[0].tagName !== 'INPUT' || tAttrs.type !== 'checkbox') 116 | && (tElement[0].tagName !== 'MD-CHECKBOX') 117 | && (!tAttrs.btnCheckbox)) { 118 | throw 'checklist-model should be applied to `input[type="checkbox"]` or `md-checkbox`.'; 119 | } 120 | 121 | if (!tAttrs.checklistValue && !tAttrs.value) { 122 | throw 'You should provide `value` or `checklist-value`.'; 123 | } 124 | 125 | // by default ngModel is 'checked', so we set it if not specified 126 | if (!tAttrs.ngModel) { 127 | // local scope var storing individual checkbox model 128 | tAttrs.$set("ngModel", "checked"); 129 | } 130 | 131 | return postLinkFn; 132 | } 133 | }; 134 | }]); -------------------------------------------------------------------------------- /web/static/lib/angular-resource/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-resource", 3 | "version": "1.4.4", 4 | "main": "./angular-resource.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "angular": "1.4.4" 8 | }, 9 | "homepage": "https://github.com/angular/bower-angular-resource", 10 | "_release": "1.4.4", 11 | "_resolution": { 12 | "type": "version", 13 | "tag": "v1.4.4", 14 | "commit": "1e95a21dfa69fe7b03c5d91dfe7aeb7cf4b82faf" 15 | }, 16 | "_source": "git://github.com/angular/bower-angular-resource.git", 17 | "_target": "1.4.x", 18 | "_originalSource": "angular-resource" 19 | } -------------------------------------------------------------------------------- /web/static/lib/angular-resource/README.md: -------------------------------------------------------------------------------- 1 | # packaged angular-resource 2 | 3 | This repo is for distribution on `npm` and `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngResource). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | You can install this package either with `npm` or with `bower`. 10 | 11 | ### npm 12 | 13 | ```shell 14 | npm install angular-resource 15 | ``` 16 | 17 | Then add `ngResource` as a dependency for your app: 18 | 19 | ```javascript 20 | angular.module('myApp', [require('angular-resource')]); 21 | ``` 22 | 23 | ### bower 24 | 25 | ```shell 26 | bower install angular-resource 27 | ``` 28 | 29 | Add a ` 33 | ``` 34 | 35 | Then add `ngResource` as a dependency for your app: 36 | 37 | ```javascript 38 | angular.module('myApp', ['ngResource']); 39 | ``` 40 | 41 | ## Documentation 42 | 43 | Documentation is available on the 44 | [AngularJS docs site](http://docs.angularjs.org/api/ngResource). 45 | 46 | ## License 47 | 48 | The MIT License 49 | 50 | Copyright (c) 2010-2015 Google, Inc. http://angularjs.org 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy 53 | of this software and associated documentation files (the "Software"), to deal 54 | in the Software without restriction, including without limitation the rights 55 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 56 | copies of the Software, and to permit persons to whom the Software is 57 | furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in 60 | all copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 63 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 64 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 65 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 66 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 67 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 68 | THE SOFTWARE. 69 | -------------------------------------------------------------------------------- /web/static/lib/angular-resource/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.4.4 3 | (c) 2010-2015 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(I,d,B){'use strict';function D(f,q){q=q||{};d.forEach(q,function(d,h){delete q[h]});for(var h in f)!f.hasOwnProperty(h)||"$"===h.charAt(0)&&"$"===h.charAt(1)||(q[h]=f[h]);return q}var x=d.$$minErr("$resource"),C=/^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;d.module("ngResource",["ng"]).provider("$resource",function(){var f=this;this.defaults={stripTrailingSlashes:!0,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}}}; 7 | this.$get=["$http","$q",function(q,h){function u(d,g){this.template=d;this.defaults=s({},f.defaults,g);this.urlParams={}}function w(y,g,l,m){function c(b,k){var c={};k=s({},g,k);r(k,function(a,k){v(a)&&(a=a());var d;if(a&&a.charAt&&"@"==a.charAt(0)){d=b;var e=a.substr(1);if(null==e||""===e||"hasOwnProperty"===e||!C.test("."+e))throw x("badmember",e);for(var e=e.split("."),n=0,g=e.length;n", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/angular/angular.js/issues" 24 | }, 25 | "homepage": "http://angularjs.org" 26 | } 27 | -------------------------------------------------------------------------------- /web/static/lib/angular-route/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-route", 3 | "version": "1.4.4", 4 | "main": "./angular-route.js", 5 | "ignore": [], 6 | "dependencies": { 7 | "angular": "1.4.4" 8 | }, 9 | "homepage": "https://github.com/angular/bower-angular-route", 10 | "_release": "1.4.4", 11 | "_resolution": { 12 | "type": "version", 13 | "tag": "v1.4.4", 14 | "commit": "253b707a425ede44798caca0031293f33b291808" 15 | }, 16 | "_source": "git://github.com/angular/bower-angular-route.git", 17 | "_target": "1.4.x", 18 | "_originalSource": "angular-route" 19 | } -------------------------------------------------------------------------------- /web/static/lib/angular-route/README.md: -------------------------------------------------------------------------------- 1 | # packaged angular-route 2 | 3 | This repo is for distribution on `npm` and `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngRoute). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | You can install this package either with `npm` or with `bower`. 10 | 11 | ### npm 12 | 13 | ```shell 14 | npm install angular-route 15 | ``` 16 | 17 | Then add `ngRoute` as a dependency for your app: 18 | 19 | ```javascript 20 | angular.module('myApp', [require('angular-route')]); 21 | ``` 22 | 23 | ### bower 24 | 25 | ```shell 26 | bower install angular-route 27 | ``` 28 | 29 | Add a ` 33 | ``` 34 | 35 | Then add `ngRoute` as a dependency for your app: 36 | 37 | ```javascript 38 | angular.module('myApp', ['ngRoute']); 39 | ``` 40 | 41 | ## Documentation 42 | 43 | Documentation is available on the 44 | [AngularJS docs site](http://docs.angularjs.org/api/ngRoute). 45 | 46 | ## License 47 | 48 | The MIT License 49 | 50 | Copyright (c) 2010-2015 Google, Inc. http://angularjs.org 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy 53 | of this software and associated documentation files (the "Software"), to deal 54 | in the Software without restriction, including without limitation the rights 55 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 56 | copies of the Software, and to permit persons to whom the Software is 57 | furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in 60 | all copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 63 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 64 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 65 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 66 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 67 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 68 | THE SOFTWARE. 69 | -------------------------------------------------------------------------------- /web/static/lib/angular-route/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.4.4 3 | (c) 2010-2015 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(p,c,C){'use strict';function v(r,h,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,f,b,d,y){function z(){k&&(g.cancel(k),k=null);l&&(l.$destroy(),l=null);m&&(k=g.leave(m),k.then(function(){k=null}),m=null)}function x(){var b=r.current&&r.current.locals;if(c.isDefined(b&&b.$template)){var b=a.$new(),d=r.current;m=y(b,function(b){g.enter(b,null,m||f).then(function(){!c.isDefined(t)||t&&!a.$eval(t)||h()});z()});l=d.scope=b;l.$emit("$viewContentLoaded"); 7 | l.$eval(w)}else z()}var l,m,k,t=b.autoscroll,w=b.onload||"";a.$on("$routeChangeSuccess",x);x()}}}function A(c,h,g){return{restrict:"ECA",priority:-400,link:function(a,f){var b=g.current,d=b.locals;f.html(d.$template);var y=c(f.contents());b.controller&&(d.$scope=a,d=h(b.controller,d),b.controllerAs&&(a[b.controllerAs]=d),f.data("$ngControllerController",d),f.children().data("$ngControllerController",d));y(a)}}}p=c.module("ngRoute",["ng"]).provider("$route",function(){function r(a,f){return c.extend(Object.create(a), 8 | f)}function h(a,c){var b=c.caseInsensitiveMatch,d={originalPath:a,regexp:a},g=d.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,c,b,d){a="?"===d?d:null;d="*"===d?d:null;g.push({name:b,optional:!!a});c=c||"";return""+(a?"":c)+"(?:"+(a?c:"")+(d&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");d.regexp=new RegExp("^"+a+"$",b?"i":"");return d}var g={};this.when=function(a,f){var b=c.copy(f);c.isUndefined(b.reloadOnSearch)&&(b.reloadOnSearch=!0); 9 | c.isUndefined(b.caseInsensitiveMatch)&&(b.caseInsensitiveMatch=this.caseInsensitiveMatch);g[a]=c.extend(b,a&&h(a,b));if(a){var d="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";g[d]=c.extend({redirectTo:a},h(d,b))}return this};this.caseInsensitiveMatch=!1;this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,f,b,d,h,p,x){function l(b){var e=s.current; 10 | (v=(n=k())&&e&&n.$$route===e.$$route&&c.equals(n.pathParams,e.pathParams)&&!n.reloadOnSearch&&!w)||!e&&!n||a.$broadcast("$routeChangeStart",n,e).defaultPrevented&&b&&b.preventDefault()}function m(){var u=s.current,e=n;if(v)u.params=e.params,c.copy(u.params,b),a.$broadcast("$routeUpdate",u);else if(e||u)w=!1,(s.current=e)&&e.redirectTo&&(c.isString(e.redirectTo)?f.path(t(e.redirectTo,e.params)).search(e.params).replace():f.url(e.redirectTo(e.pathParams,f.path(),f.search())).replace()),d.when(e).then(function(){if(e){var a= 11 | c.extend({},e.resolve),b,f;c.forEach(a,function(b,e){a[e]=c.isString(b)?h.get(b):h.invoke(b,null,null,e)});c.isDefined(b=e.template)?c.isFunction(b)&&(b=b(e.params)):c.isDefined(f=e.templateUrl)&&(c.isFunction(f)&&(f=f(e.params)),c.isDefined(f)&&(e.loadedTemplateUrl=x.valueOf(f),b=p(f)));c.isDefined(b)&&(a.$template=b);return d.all(a)}}).then(function(f){e==s.current&&(e&&(e.locals=f,c.copy(e.params,b)),a.$broadcast("$routeChangeSuccess",e,u))},function(b){e==s.current&&a.$broadcast("$routeChangeError", 12 | e,u,b)})}function k(){var a,b;c.forEach(g,function(d,g){var q;if(q=!b){var h=f.path();q=d.keys;var l={};if(d.regexp)if(h=d.regexp.exec(h)){for(var k=1,m=h.length;k", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/angular/angular.js/issues" 24 | }, 25 | "homepage": "http://angularjs.org" 26 | } 27 | -------------------------------------------------------------------------------- /web/static/lib/angular/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.4.4", 4 | "main": "./angular.js", 5 | "ignore": [], 6 | "dependencies": {}, 7 | "homepage": "https://github.com/angular/bower-angular", 8 | "_release": "1.4.4", 9 | "_resolution": { 10 | "type": "version", 11 | "tag": "v1.4.4", 12 | "commit": "f00cff5525db7c853f42854079a31936d400f99b" 13 | }, 14 | "_source": "git://github.com/angular/bower-angular.git", 15 | "_target": "1.4.x", 16 | "_originalSource": "angular" 17 | } -------------------------------------------------------------------------------- /web/static/lib/angular/README.md: -------------------------------------------------------------------------------- 1 | # packaged angular 2 | 3 | This repo is for distribution on `npm` and `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | You can install this package either with `npm` or with `bower`. 10 | 11 | ### npm 12 | 13 | ```shell 14 | npm install angular 15 | ``` 16 | 17 | Then add a ` 21 | ``` 22 | 23 | Or `require('angular')` from your code. 24 | 25 | ### bower 26 | 27 | ```shell 28 | bower install angular 29 | ``` 30 | 31 | Then add a ` 35 | ``` 36 | 37 | ## Documentation 38 | 39 | Documentation is available on the 40 | [AngularJS docs site](http://docs.angularjs.org/). 41 | 42 | ## License 43 | 44 | The MIT License 45 | 46 | Copyright (c) 2010-2015 Google, Inc. http://angularjs.org 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a copy 49 | of this software and associated documentation files (the "Software"), to deal 50 | in the Software without restriction, including without limitation the rights 51 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 52 | copies of the Software, and to permit persons to whom the Software is 53 | furnished to do so, subject to the following conditions: 54 | 55 | The above copyright notice and this permission notice shall be included in 56 | all copies or substantial portions of the Software. 57 | 58 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 59 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 60 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 61 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 62 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 63 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 64 | THE SOFTWARE. 65 | -------------------------------------------------------------------------------- /web/static/lib/angular/angular-csp.css: -------------------------------------------------------------------------------- 1 | /* Include this file in your html if you are using the CSP mode. */ 2 | 3 | @charset "UTF-8"; 4 | 5 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], 6 | .ng-cloak, .x-ng-cloak, 7 | .ng-hide:not(.ng-hide-animate) { 8 | display: none !important; 9 | } 10 | 11 | ng\:form { 12 | display: block; 13 | } 14 | 15 | .ng-animate-shim { 16 | visibility:hidden; 17 | } 18 | 19 | .ng-anchor { 20 | position:absolute; 21 | } 22 | -------------------------------------------------------------------------------- /web/static/lib/angular/angular.min.js.gzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shunfei/aproxy/6f66a61dbfef4dfd67e93ba18eca8a2a214322ae/web/static/lib/angular/angular.min.js.gzip -------------------------------------------------------------------------------- /web/static/lib/angular/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.4.4", 4 | "main": "./angular.js", 5 | "ignore": [], 6 | "dependencies": { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /web/static/lib/angular/index.js: -------------------------------------------------------------------------------- 1 | require('./angular'); 2 | module.exports = angular; 3 | -------------------------------------------------------------------------------- /web/static/lib/angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.4.4", 4 | "description": "HTML enhanced for web apps", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/angular/angular.js.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "framework", 16 | "browser", 17 | "client-side" 18 | ], 19 | "author": "Angular Core Team ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/angular/angular.js/issues" 23 | }, 24 | "homepage": "http://angularjs.org" 25 | } 26 | -------------------------------------------------------------------------------- /web/static/lib/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shunfei/aproxy/6f66a61dbfef4dfd67e93ba18eca8a2a214322ae/web/static/lib/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /web/static/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shunfei/aproxy/6f66a61dbfef4dfd67e93ba18eca8a2a214322ae/web/static/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /web/static/lib/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shunfei/aproxy/6f66a61dbfef4dfd67e93ba18eca8a2a214322ae/web/static/lib/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /web/static/lib/bootstrap/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /web/static/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | aProxy Login 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 53 | 54 |
55 | 97 |
98 | 99 | 100 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /web/static/partials/authority/detail.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | Edit Authority For: {{authority.Email}} 6 |
7 |
8 |
9 |
10 | 16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 | 27 |
28 |
29 |
30 | 31 |
32 | 35 |
36 |
37 |
38 | 39 |
40 | 41 |
42 |
43 |
44 | 45 |
46 | 47 |
48 |
49 |
50 |
51 | 52 | Cancel 53 |
54 |
55 |
56 |
57 |
58 | 59 | 60 |
-------------------------------------------------------------------------------- /web/static/partials/authority/list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Add 4 | 5 | 6 |
7 |
8 | 9 |
10 | 11 |
12 |
13 |
14 | 15 | 16 | Authority List 17 |
18 |
19 |
20 |
21 | 22 | {{authority.Email}} DEL {{authority.Desc}} 23 |
24 |
Allow: 25 | 26 | {{allow}} , 27 | 28 |
29 |
Deny: 30 | 31 | {{deny}} , 32 | 33 |
34 |
35 |
36 |
37 |
38 |
-------------------------------------------------------------------------------- /web/static/partials/authority/new.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | Add New Authority 5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | 23 |
24 | 26 |
27 |
28 |
29 | 30 |
31 | 34 |
35 |
36 |
37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 | 46 |
47 |
48 |
49 |
50 | 51 | Cancel 52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 |
-------------------------------------------------------------------------------- /web/static/partials/backend-conf/detail.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | Edit: {{oldHostName}} 5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | 23 |
24 | 26 |
27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 |
36 | 37 | Cancel 38 |
39 |
40 |
41 |
42 |
43 | 44 | 45 |
-------------------------------------------------------------------------------- /web/static/partials/backend-conf/list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Add 4 | 5 | Backend Config List 6 |
7 |
8 |
9 |
10 | {{bconf.HostName}} {{bconf.Desc}} DEL 11 |
    12 |
  • {{upstream}}
  • 13 |
14 |
15 |
16 |
17 |
-------------------------------------------------------------------------------- /web/static/partials/backend-conf/new.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | Add New Backend Config 5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | 23 |
24 | 26 |
27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 |
36 | 37 | Cancel 38 |
39 |
40 |
41 |
42 |
43 | 44 | 45 |
-------------------------------------------------------------------------------- /web/static/partials/role/detail.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | Edit Role: {{role.Name}} 6 |
7 |
8 |
9 |
10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 | 26 |
27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 |
36 | 37 | Cancel 38 |
39 |
40 |
41 |
42 |
43 | 44 | 45 |
-------------------------------------------------------------------------------- /web/static/partials/role/list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Add 4 | 5 | Role List 6 |
7 |
8 |
9 |
10 | 11 | {{role.Name}} DEL 12 |
{{role.Desc}}
13 |
14 |
Allow: 15 | {{allow}} , 16 |
17 |
Deny: 18 | {{deny}} , 19 |
20 |
21 |
22 |
23 |
24 |
-------------------------------------------------------------------------------- /web/static/partials/role/new.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | Add New Role 5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | 23 |
24 | 25 |
26 |
27 |
28 | 29 |
30 | 31 |
32 |
33 |
34 |
35 | 36 | Cancel 37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 |
-------------------------------------------------------------------------------- /web/static/partials/users/detail.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | Edit User: {{user.Email}} 6 |
7 |
8 |
9 |
10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 |
18 | 19 | Cancel 20 |
21 |
22 |
23 |
24 |
25 | 26 | 27 |
-------------------------------------------------------------------------------- /web/static/partials/users/list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Add 4 | 5 | User List 6 |
7 |
8 |
9 | 13 |
14 |
15 |
-------------------------------------------------------------------------------- /web/static/partials/users/new.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | Add New User 6 |
7 |
8 |
9 |
10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 |
24 | 25 | Cancel 26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 |
--------------------------------------------------------------------------------