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