Commit 4a0f0122 authored by Peter Cheng's avatar Peter Cheng

開發完成

parent b4ba1845
No related merge requests found
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
\.env
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 關閉LDAP連線
func (lp *LDAP) Close(l *ldap.Conn) {
l.Close()
}
//Login 透過使用者登入LDAP
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
}
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 下載存取Token頁面
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
}
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
}
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)
}
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 取得Redis資料
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 設定Redis資料
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
}
package libs
import (
"fmt"
"log"
"os"
"strconv"
"time"
"github.com/RangelReale/osin"
)
//oauthClients 人資系統OAuth 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 關閉Client
func (s *Storage) Close() {
}
//GetClient 取得Client
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 設定Client
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 取得人資系統OAuth 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 從Redis取得驗証碼
func (s *Storage) loadAuthorize2Redis(key string) string {
r := Redis{}.New()
result := r.Get(key)
return result
}
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()
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment