From 4a0f0122884ae47d23fa27b0ce9b07ca7551aed8 Mon Sep 17 00:00:00 2001 From: Peter Cheng <teed7334@gmail.com> Date: Mon, 11 Nov 2019 18:18:30 +0800 Subject: [PATCH] =?UTF-8?q?=E9=96=8B=E7=99=BC=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.swp | 15 ++++ .gitignore | 2 + libs/ldap.go | 69 ++++++++++++++++++ libs/login.go | 75 ++++++++++++++++++++ libs/mysql.go | 31 ++++++++ libs/oauth.go | 65 +++++++++++++++++ libs/redis.go | 55 +++++++++++++++ libs/storage.go | 183 ++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 16 +++++ 9 files changed, 511 insertions(+) create mode 100644 .env.swp create mode 100644 .gitignore create mode 100644 libs/ldap.go create mode 100644 libs/login.go create mode 100644 libs/mysql.go create mode 100644 libs/oauth.go create mode 100644 libs/redis.go create mode 100644 libs/storage.go create mode 100644 main.go diff --git a/.env.swp b/.env.swp new file mode 100644 index 0000000..a8b62b2 --- /dev/null +++ b/.env.swp @@ -0,0 +1,15 @@ +MYSQL_HOST = "" +MYSQL_USER = "" +MYSQL_PASSWORD = "" +MYSQL_DATABASE = "" +MYSQL_CHARSET = "utf8mb4" +MYSQL_PARSETIME = "true" +MYSQL_LOC = "Local" +LDAP_HOST_NAME = "ldap.example.com" +LDAP_HOST_PORT = 389 +LDAP_DC_NAME = "dc=example,dc=com" +LDAP_USER_NAME = "admin" +LDAP_USER_PASSWORD = "" +REDIS_PROTOCOL = "tcp" +REDIS_HOST = "localhost:6379" +EXPIRE_TIME = 86400 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65dedec --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +\.env diff --git a/libs/ldap.go b/libs/ldap.go new file mode 100644 index 0000000..fbd7d71 --- /dev/null +++ b/libs/ldap.go @@ -0,0 +1,69 @@ +package libs + +import ( + "fmt" + "log" + "os" + + "gopkg.in/ldap.v3" +) + +//LDAP �㮾��𡏭身摰� +type LDAP struct { + host string + port string + user string + password string + dc string +} + +//New 撱箸�见�� +func (lp LDAP) New() *LDAP { + lp.host = os.Getenv("LDAP_HOST_NAME") + lp.port = os.Getenv("LDAP_HOST_PORT") + lp.user = os.Getenv("LDAP_USER_NAME") + lp.password = os.Getenv("LDAP_USER_PASSWORD") + lp.dc = os.Getenv("LDAP_DC_NAME") + return &lp +} + +//Connect ���𡁜�LDAP +func (lp *LDAP) Connect() *ldap.Conn { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%s", lp.host, lp.port)) + if err != nil { + log.Panicln(err) + } + + err = l.Bind(fmt.Sprintf("cn=%s,%s", lp.user, lp.dc), lp.password) + if err != nil { + log.Panicln(err) + } + return l +} + +//Close ��𣈯�𧜵DAP���� +func (lp *LDAP) Close(l *ldap.Conn) { + l.Close() +} + +//Login �誯�𦒘蝙�鍂���蒈�他DAP +func (lp *LDAP) Login(l *ldap.Conn, user string, password string) bool { + searchRequest := ldap.NewSearchRequest( + lp.dc, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", user), + []string{"dn"}, + nil, + ) + sr, err := l.Search(searchRequest) + if err != nil { + log.Panicln(err) + } + userdn := sr.Entries[0].DN + err = l.Bind(userdn, password) + if err != nil { + log.Panicln(err) + return false + } + return true +} diff --git a/libs/login.go b/libs/login.go new file mode 100644 index 0000000..0593cbf --- /dev/null +++ b/libs/login.go @@ -0,0 +1,75 @@ +package libs + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/RangelReale/osin" +) + +//Login �㮾��𡏭身摰� +type Login struct { + user string + password string +} + +//New 撱箸�见�� +func (l Login) New() *Login { + return &l +} + +//HandleLoginPage �蒈�仿��𢒰 +func (l *Login) HandleLoginPage(ar *osin.AuthorizeRequest, w http.ResponseWriter, r *http.Request) *Login { + r.ParseForm() + l.user = "" + l.password = "" + + if r.Method == "POST" { + l.user = r.FormValue("user") + l.password = r.FormValue("password") + return l + } + + w.Write([]byte("<html><body>")) + + w.Write([]byte(fmt.Sprintf("LOGIN %s (use test/test)<br/>", ar.Client.GetId()))) + w.Write([]byte(fmt.Sprintf("<form action=\"/authorize?%s\" method=\"POST\">", r.URL.RawQuery))) + + w.Write([]byte("Login: <input type=\"text\" name=\"user\" /><br/>")) + w.Write([]byte("Password: <input type=\"password\" name=\"password\" /><br/>")) + w.Write([]byte("<input type=\"submit\"/>")) + + w.Write([]byte("</form>")) + + w.Write([]byte("</body></html>")) + + return l +} + +//DownloadAccessToken 銝贝�匧�睃�巁oken���𢒰 +func (l *Login) DownloadAccessToken(url string, auth *osin.BasicAuth, output map[string]interface{}) error { + // download access token + preq, err := http.NewRequest("POST", url, nil) + if err != nil { + return err + } + if auth != nil { + preq.SetBasicAuth(auth.Username, auth.Password) + } + + pclient := &http.Client{} + presp, err := pclient.Do(preq) + if err != nil { + return err + } + + if presp.StatusCode != 200 { + return errors.New("Invalid status code") + } + + jdec := json.NewDecoder(presp.Body) + err = jdec.Decode(&output) + return err +} diff --git a/libs/mysql.go b/libs/mysql.go new file mode 100644 index 0000000..15e8f6e --- /dev/null +++ b/libs/mysql.go @@ -0,0 +1,31 @@ +package libs + +import ( + "fmt" + "os" + + "github.com/jinzhu/gorm" +) + +//MySQL �㮾��𡏭身摰� +type MySQL struct { + Db *gorm.DB +} + +//New 撱箸�见�� +func (my MySQL) New() *MySQL { + host := os.Getenv("MYSQL_HOST") + user := os.Getenv("MYSQL_USER") + password := os.Getenv("MYSQL_PASSWORD") + database := os.Getenv("MYSQL_DATABASE") + charset := os.Getenv("MYSQL_CHARSET") + parseTime := os.Getenv("MYSQL_PARSETIME") + loc := os.Getenv("MYSQL_LOC") + dsn := fmt.Sprintf("%s:%s@(%s)/%s?charset=%s&parseTime=%s&loc=%s", user, password, host, database, charset, parseTime, loc) + var err error + my.Db, err = gorm.Open("mysql", dsn) + if err != nil { + panic(err) + } + return &my +} diff --git a/libs/oauth.go b/libs/oauth.go new file mode 100644 index 0000000..12665d0 --- /dev/null +++ b/libs/oauth.go @@ -0,0 +1,65 @@ +package libs + +import ( + "net/http" + + "github.com/RangelReale/osin" +) + +//OAuth �㮾��𡏭身摰� +type OAuth struct { + server *osin.Server +} + +//New 撱箸�见�� +func (oh OAuth) New() OAuth { + storage := Storage{}.New() + oh.server = osin.NewServer(osin.NewServerConfig(), storage) + return oh +} + +//APIs �鐤�㙈��API頝臬�穃�𡑒” +func (oh *OAuth) APIs() { + http.HandleFunc("/authorize", oh.authorize) + http.HandleFunc("/token", oh.token) + http.ListenAndServe(":14000", nil) +} + +func (oh *OAuth) open(r *http.Request) (*osin.Response, *osin.AuthorizeRequest) { + resp := oh.server.NewResponse() + defer resp.Close() + ar := oh.server.HandleAuthorizeRequest(resp, r) + return resp, ar +} + +func (oh *OAuth) authorize(w http.ResponseWriter, r *http.Request) { + resp, ar := oh.open(r) + + if ar != nil { + l := Login{}.New() + result := l.HandleLoginPage(ar, w, r) + if (result.user == "" || result.password == "") || !oh.authenticate(result.user, result.password) { + return + } + ar.Authorized = true + oh.server.FinishAuthorizeRequest(resp, r, ar) + } + osin.OutputJSON(resp, w, r) +} + +func (oh *OAuth) authenticate(user, password string) bool { + l := LDAP{}.New() + conn := l.Connect() + defer l.Close(conn) + validated := l.Login(conn, user, password) + return validated +} + +func (oh *OAuth) token(w http.ResponseWriter, r *http.Request) { + resp, ar := oh.open(r) + if ar != nil { + ar.Authorized = true + oh.server.FinishAuthorizeRequest(resp, r, ar) + } + osin.OutputJSON(resp, w, r) +} diff --git a/libs/redis.go b/libs/redis.go new file mode 100644 index 0000000..bf04e5c --- /dev/null +++ b/libs/redis.go @@ -0,0 +1,55 @@ +package libs + +import ( + "log" + "os" + + "github.com/garyburd/redigo/redis" +) + +//Redis �㮾��𡏭身摰� +type Redis struct { + client redis.Conn +} + +//New 撱箸�见�� +func (r Redis) New() *Redis { + protocol := os.Getenv("REDIS_PROTOCOL") + host := os.Getenv("REDIS_HOST") + var err error + r.client, err = redis.Dial(protocol, host) + if err != nil { + log.Panicln(err) + } + return &r +} + +//SetExpire 閮剖�朞���䠷�擧������ +func (r *Redis) SetExpire(key string, expire int) { + _, err := r.client.Do("EXPIRE", key) + if err != nil { + log.Panicln(err) + } + defer r.client.Close() +} + +//Get ��硋�妔edis鞈��� +func (r *Redis) Get(key string) string { + value, err := redis.String(r.client.Do("get", key)) + if err != nil { + log.Panicln(err) + } + defer r.client.Close() + return value +} + +//Set 閮剖�鑹edis鞈��� +func (r *Redis) Set(key, value string) bool { + _, err := r.client.Do("set", key, value) + if err != nil { + log.Panicln(err) + return false + } + defer r.client.Close() + return true +} diff --git a/libs/storage.go b/libs/storage.go new file mode 100644 index 0000000..87ed772 --- /dev/null +++ b/libs/storage.go @@ -0,0 +1,183 @@ +package libs + +import ( + "fmt" + "log" + "os" + "strconv" + "time" + + "github.com/RangelReale/osin" +) + +//oauthClients 鈭箄��頂蝯屠Auth Client鞈��躰”蝯鞉�� +type oauthClients struct { + ClientID string + ClientSecret string + RedirectURI string + GrantTypes string + Scope string + UserID string +} + +//Storage OAuth 鞈��嗵�鞉�� +type Storage struct { + clients map[string]osin.Client + authorize map[string]*osin.AuthorizeData + access map[string]*osin.AccessData + refresh map[string]string +} + +//New 撱箸�见�� +func (s Storage) New() *Storage { + s.clients = make(map[string]osin.Client) + clients := s.getClients() + expire64, err := strconv.Atoi(os.Getenv("EXPIRE_TIME")) + expire := int32(expire64) + if err != nil { + log.Panicln(err) + } + for _, item := range clients { + s.clients[item.ClientID] = &osin.DefaultClient{ + Id: item.ClientID, + Secret: item.ClientSecret, + RedirectUri: item.RedirectURI, + } + s.authorize = make(map[string]*osin.AuthorizeData) + s.access = make(map[string]*osin.AccessData) + s.refresh = make(map[string]string) + s.access[item.ClientID] = &osin.AccessData{ + Client: s.clients[item.ClientID], + AuthorizeData: s.authorize[item.ClientID], + AccessToken: item.ClientID, + ExpiresIn: expire, + CreatedAt: time.Now(), + } + } + return &s +} + +//Clone 銴�ˊ�鰵��Client +func (s *Storage) Clone() osin.Storage { + return s +} + +//Close ��𣈯�攊lient +func (s *Storage) Close() { +} + +//GetClient ��硋�䊼lient +func (s *Storage) GetClient(id string) (osin.Client, error) { + fmt.Printf("GetClient: %s\n", id) + if c, ok := s.clients[id]; ok { + return c, nil + } + return nil, osin.ErrNotFound +} + +//SetClient 閮剖�鋴lient +func (s *Storage) SetClient(id string, client osin.Client) error { + fmt.Printf("SetClient: %s\n", id) + s.clients[id] = client + return nil +} + +//SaveAuthorize �脣�䀹���𡃏迂�虾 +func (s *Storage) SaveAuthorize(data *osin.AuthorizeData) error { + fmt.Printf("SaveAuthorize: %s\n", data.Code) + s.saveAuthorize2Redis(data.Code, "1") + s.authorize[data.Code] = data + return nil +} + +//LoadAuthorize 霈���𡝗���𡃏迂�虾 +func (s *Storage) LoadAuthorize(code string) (*osin.AuthorizeData, error) { + fmt.Printf("LoadAuthorize: %s\n", code) + if d, ok := s.authorize[code]; ok { + return d, nil + } + return nil, osin.ErrNotFound +} + +//RemoveAuthorize 蝘駁膄����𡃏迂�虾 +func (s *Storage) RemoveAuthorize(code string) error { + fmt.Printf("RemoveAuthorize: %s\n", code) + delete(s.authorize, code) + return nil +} + +//SaveAccess �脣�睃�睃�𤥁���� +func (s *Storage) SaveAccess(data *osin.AccessData) error { + fmt.Printf("SaveAccess: %s\n", data.AccessToken) + s.access[data.AccessToken] = data + if data.RefreshToken != "" { + s.refresh[data.RefreshToken] = data.AccessToken + } + return nil +} + +//LoadAccess 頛匧�亙�睃�𤥁���� +func (s *Storage) LoadAccess(code string) (*osin.AccessData, error) { + fmt.Printf("LoadAccess: %s\n", code) + if d, ok := s.access[code]; ok { + return d, nil + } + return nil, osin.ErrNotFound +} + +//RemoveAccess 蝘駁膄摮睃�𤥁���� +func (s *Storage) RemoveAccess(code string) error { + fmt.Printf("RemoveAccess: %s\n", code) + delete(s.access, code) + return nil +} + +//LoadRefresh 霈��交�𤤿䔄Token +func (s *Storage) LoadRefresh(code string) (*osin.AccessData, error) { + fmt.Printf("LoadRefresh: %s\n", code) + if d, ok := s.refresh[code]; ok { + return s.LoadAccess(d) + } + return nil, osin.ErrNotFound +} + +//RemoveRefresh 蝘駁膄��𤤿䔄Token +func (s *Storage) RemoveRefresh(code string) error { + fmt.Printf("RemoveRefresh: %s\n", code) + delete(s.refresh, code) + return nil +} + +//getClients ��硋�𦯀犖鞈�頂蝯屠Auth Client鞈��躰” +func (s *Storage) getClients() []*oauthClients { + mysql := MySQL{}.New() + list := []*oauthClients{} + err := mysql.Db.Find(&list).Error + if err != nil { + log.Fatal(err) + } + return list +} + +//saveAuthorize2Redis 撠��𡑒釆蝣澆神��Redis�縧 +func (s *Storage) saveAuthorize2Redis(key, value string) bool { + r := Redis{}.New() + result := r.Set(key, value) + if !result { + return false + } + expire, err := strconv.Atoi(os.Getenv("EXPIRE_TIME")) + if err != nil { + log.Panicln(err) + return false + } + r.SetExpire(key, expire) + return true +} + +//loadAuthorize2Redis 敺冚edis��硋�烾�𡑒釆蝣� +func (s *Storage) loadAuthorize2Redis(key string) string { + r := Redis{}.New() + result := r.Get(key) + return result +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..a01df90 --- /dev/null +++ b/main.go @@ -0,0 +1,16 @@ +package main + +import ( + _ "github.com/jinzhu/gorm/dialects/mysql" + _ "github.com/joho/godotenv/autoload" + "github.com/teed7334-restore/oauth_server/libs" +) + +func main() { + doOauth() +} + +func doOauth() { + o := libs.OAuth{}.New() + o.APIs() +} -- 2.18.1