Jelajahi Sumber

Initial commit

Sasan Torabkheslat 3 tahun lalu
melakukan
74b5b70d5e
7 mengubah file dengan 423 tambahan dan 0 penghapusan
  1. 3 0
      .gitignore
  2. 38 0
      Gopkg.toml
  3. 32 0
      README.md
  4. 217 0
      handler.go
  5. 60 0
      main.go
  6. 25 0
      middleware.go
  7. 48 0
      token.go

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+vendor
+.idea
+Gopkg.lock

+ 38 - 0
Gopkg.toml

@@ -0,0 +1,38 @@
+# Gopkg.toml example
+#
+# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+#   name = "github.com/user/project"
+#   version = "1.0.0"
+#
+# [[constraint]]
+#   name = "github.com/user/project2"
+#   branch = "dev"
+#   source = "github.com/myfork/project2"
+#
+# [[override]]
+#   name = "github.com/x/y"
+#   version = "2.4.0"
+#
+# [prune]
+#   non-go = false
+#   go-tests = true
+#   unused-packages = true
+
+
+[prune]
+  go-tests = true
+  unused-packages = true
+
+[[constraint]]
+  name = "github.com/dgrijalva/jwt-go"
+  version = "3.2.0"
+
+[[constraint]]
+  name = "github.com/labstack/echo"
+  version = "3.3.6"

+ 32 - 0
README.md

@@ -0,0 +1,32 @@
+# ZiTel WebService Template by Sassan
+
+A template with the following features for internal projects of Sysbo-Team at ZiTel
+
+* JWT authentication mechanism
+* FreeIPA
+
+## How to run
+openssl s_client -connect ipa.sf.faraborddi.dc:443 >/usr/local/share/ca-certificates/ipa.crt;update-ca-certificates
+
+`dep ensure`
+
+`go build .`
+
+./CMD IP PORT
+
+
+
+## Client
+### Request:
+curl --location --request POST 'http://127.0.0.1:8000/login' \
+--header 'Content-Type: application/x-www-form-urlencoded' \
+--data-urlencode 'username=s.torabkheslat' \
+--data-urlencode 'password=*******'
+
+
+### Response:
+{
+    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNjM3NDIzMjE0LCJuYW1lIjoicy50b3JhYmtoZXNsYXQiLCJzdWIiOjF9.EWS0JQ0muAXSnI0pyOpbsqJlDPnX37Pi7kPzT4EOZdU",
+    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Mzc1MDg3MTQsInN1YiI6MX0.l-ziJVifwt3vltgJn419Uka4s_oOJBo1KGX90EI63oI"
+}
+

+ 217 - 0
handler.go

@@ -0,0 +1,217 @@
+package main
+
+import (
+	"crypto/tls"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"github.com/dgrijalva/jwt-go"
+	"github.com/labstack/echo"
+)
+
+type handler struct{}
+type userInfo struct {
+	Result struct {
+		Result struct {
+			Sshpubkeyfp      []string `json:"sshpubkeyfp"`
+			HasKeytab        bool     `json:"has_keytab"`
+			Ipasshpubkey     []string `json:"ipasshpubkey"`
+			Cn               []string `json:"cn"`
+			Krbcanonicalname []string `json:"krbcanonicalname"`
+			Krbticketflags   []string `json:"krbticketflags"`
+			MemberofGroup    []string `json:"memberof_group"`
+			HasPassword      bool     `json:"has_password"`
+			Homedirectory    []string `json:"homedirectory"`
+			Nsaccountlock    bool     `json:"nsaccountlock"`
+			UID              []string `json:"uid"`
+			Title            []string `json:"title"`
+			Loginshell       []string `json:"loginshell"`
+			Uidnumber        []string `json:"uidnumber"`
+			Preserved        bool     `json:"preserved"`
+			Krbextradata     []struct {
+				Base64 string `json:"__base64__"`
+			} `json:"krbextradata"`
+			Mail                     []string `json:"mail"`
+			MemberofindirectHbacrule []string `json:"memberofindirect_hbacrule"`
+			Dn                       string   `json:"dn"`
+			Displayname              []string `json:"displayname"`
+			Mepmanagedentry          []string `json:"mepmanagedentry"`
+			Ipauniqueid              []string `json:"ipauniqueid"`
+			Krbloginfailedcount      []string `json:"krbloginfailedcount"`
+			Krbpwdpolicyreference    []string `json:"krbpwdpolicyreference"`
+			Krbprincipalname         []string `json:"krbprincipalname"`
+			Givenname                []string `json:"givenname"`
+			Krblastadminunlock       []struct {
+				Datetime string `json:"__datetime__"`
+			} `json:"krblastadminunlock"`
+			Krbpasswordexpiration []struct {
+				Datetime string `json:"__datetime__"`
+			} `json:"krbpasswordexpiration"`
+			Krblastfailedauth []struct {
+				Datetime string `json:"__datetime__"`
+			} `json:"krblastfailedauth"`
+			Objectclass      []string `json:"objectclass"`
+			Gidnumber        []string `json:"gidnumber"`
+			Gecos            []string `json:"gecos"`
+			Sn               []string `json:"sn"`
+			MemberofSudorule []string `json:"memberof_sudorule"`
+			Krblastpwdchange []struct {
+				Datetime string `json:"__datetime__"`
+			} `json:"krblastpwdchange"`
+			Initials []string `json:"initials"`
+		} `json:"result"`
+		Value   string      `json:"value"`
+		Summary interface{} `json:"summary"`
+	} `json:"result"`
+	Version   string      `json:"version"`
+	Error     interface{} `json:"error"`
+	ID        int         `json:"id"`
+	Principal string      `json:"principal"`
+}
+
+// Most of the code is taken from the echo guide
+// https://echo.labstack.com/cookbook/jwt
+func (h *handler) login(c echo.Context) error {
+	username := c.FormValue("username")
+	password := c.FormValue("password")
+	_url := "https://ipa.sf.faraborddi.dc/ipa/session/login_password"
+	method := "POST"
+	params := url.Values{}
+	params.Add("user", username)
+	params.Add("password", password)
+	payload := strings.NewReader(params.Encode())
+	tr := &http.Transport{
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+	}
+	client := &http.Client{Transport: tr}
+	req, err := http.NewRequest(method, _url, payload)
+	audit("Recieved Login request from: " + RealIP)
+	if err != nil {
+		fmt.Println(err)
+	}
+	req.Header.Add("Referer", "https://ipa.sf.faraborddi.dc/ipa")
+	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+	req.Header.Add("Accept", "text/plain")
+	res, err := client.Do(req)
+	cockie := res.Cookies()
+	defer res.Body.Close()
+	//fmt.Println(res.StatusCode)
+	if res.StatusCode == 200 {
+		user := getUserInfo(cockie, username)
+		//fmt.Println(user.Result.Value)
+		tokens, err := generateTokenPair(user)
+		if err != nil {
+			return err
+		}
+
+		return c.JSON(http.StatusOK, tokens)
+	}
+
+	return echo.ErrUnauthorized
+}
+func getUserInfo(cockie []*http.Cookie, username string) userInfo {
+	url := "https://ipa.sf.faraborddi.dc/ipa/session/json"
+	method := "POST"
+	_json := fmt.Sprintf(`
+{
+    "method": "user_show",
+    "params": [
+        [
+            "%s"
+        ],
+        {
+            "all": true,
+            "version": "2.215"
+        }
+    ],
+    "id": 0
+}
+`, username)
+
+	payload := strings.NewReader(_json)
+	tr := &http.Transport{
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+	}
+	client := &http.Client{Transport: tr}
+	req, err := http.NewRequest(method, url, payload)
+
+	if err != nil {
+		fmt.Println(err)
+	}
+	req.Header.Add("Referer", "https://ipa.sf.faraborddi.dc/ipa")
+	req.Header.Add("Content-Type", "application/json")
+	req.Header.Add("Accept", "text/plain")
+	req.Header.Add("Cookie", cockie[0].Raw)
+	res, err := client.Do(req)
+	defer res.Body.Close()
+	body, err := ioutil.ReadAll(res.Body)
+	user := userInfo{}
+	json.Unmarshal(body, &user)
+	//fmt.Println(user.Result.Value)
+	//fmt.Println(user.Result.Result.MemberofGroup)
+	return user
+}
+
+// This is the api to refresh tokens
+// Most of the code is taken from the jwt-go package's sample codes
+// https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac
+//func (h *handler) token(c echo.Context) error {
+//	type tokenReqBody struct {
+//		RefreshToken string `json:"refresh_token"`
+//	}
+//	tokenReq := tokenReqBody{}
+//	c.Bind(&tokenReq)
+//
+//	// Parse takes the token string and a function for looking up the key.
+//	// The latter is especially useful if you use multiple keys for your application.
+//	// The standard is to use 'kid' in the head of the token to identify
+//	// which key to use, but the parsed token (head and claims) is provided
+//	// to the callback, providing flexibility.
+//	token, err := jwt.Parse(tokenReq.RefreshToken, func(token *jwt.Token) (interface{}, error) {
+//		// Don't forget to validate the alg is what you expect:
+//		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+//			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
+//		}
+//
+//		// hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key")
+//		return []byte("secret"), nil
+//	})
+//
+//	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
+//		// Get the user record from database or
+//		// run through your business logic to verify if the user can log in
+//		if int(claims["sub"].(float64)) == 1 {
+//
+//			newTokenPair, err := generateTokenPair()
+//			if err != nil {
+//				return err
+//			}
+//
+//			return c.JSON(http.StatusOK, newTokenPair)
+//		}
+//
+//		return echo.ErrUnauthorized
+//	}
+//
+//	return err
+//}
+
+// Most of the code is taken from the echo guide
+// https://echo.labstack.com/cookbook/jwt
+func (h *handler) private(c echo.Context) error {
+	user := c.Get("user").(*jwt.Token)
+	claims := user.Claims.(jwt.MapClaims)
+	name := claims["name"].(string)
+	return c.String(http.StatusOK, "Welcome "+name+"!")
+}
+
+func (h *handler) findMAC(c echo.Context) error {
+	user := c.Get("user").(*jwt.Token)
+	claims := user.Claims.(jwt.MapClaims)
+	name := claims["name"].(string)
+	return c.String(http.StatusOK, "Welcome "+name+"!")
+}

+ 60 - 0
main.go

@@ -0,0 +1,60 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	"log/syslog"
+	"net/http"
+	"os"
+
+	"github.com/labstack/echo"
+)
+
+var _appversion string = "0.1"
+var _appname string = "ZiTel-Sysbo-WS"
+
+func audit(txt string) {
+
+	syslogger, err := syslog.New(syslog.LOG_INFO, _appname)
+	if err != nil {
+		log.Fatalln(err)
+	}
+	log.SetOutput(syslogger)
+	log.Println(txt)
+
+}
+
+var RealIP string
+
+func extractIP(next echo.HandlerFunc) echo.HandlerFunc {
+	return func(c echo.Context) error {
+		RealIP = c.RealIP()
+		audit("Recieved request from: " + RealIP)
+		return next(c)
+	}
+}
+func main() {
+	if len(os.Args) != 3 {
+		fmt.Println("Wrong Usage:\n\t ./CMD IP Port")
+		audit("Application in the wrong way")
+		os.Exit(1)
+	}
+	echoHandler := echo.New()
+	echoHandler.Use(extractIP)
+	audit("Application " + _appname + " (" + _appversion + ") Started by " + os.Getenv("USER"))
+	echoHandler.GET("/", func(c echo.Context) error {
+		return c.String(http.StatusOK, "Hello, World!")
+	})
+
+	h := &handler{}
+	echoHandler.POST("/login", h.login)
+
+	echoHandler.GET("/private", h.private, isLoggedIn)
+	echoHandler.GET("/findMAC", h.findMAC, isLoggedIn)
+
+	echoHandler.GET("/admin", h.private, isLoggedIn, isAdmin)
+
+	//echoHandler.POST("/token", h.token)
+
+	echoHandler.Logger.Fatal(echoHandler.Start(os.Args[1] + ":" + os.Args[2]))
+}

+ 25 - 0
middleware.go

@@ -0,0 +1,25 @@
+package main
+
+import (
+	"github.com/dgrijalva/jwt-go"
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/middleware"
+)
+
+var isLoggedIn = middleware.JWTWithConfig(middleware.JWTConfig{
+	SigningKey: []byte("secret"),
+})
+
+func isAdmin(next echo.HandlerFunc) echo.HandlerFunc {
+	return func(c echo.Context) error {
+		user := c.Get("user").(*jwt.Token)
+		claims := user.Claims.(jwt.MapClaims)
+		isAdmin := claims["admin"].(bool)
+
+		if isAdmin == false {
+			return echo.ErrUnauthorized
+		}
+
+		return next(c)
+	}
+}

+ 48 - 0
token.go

@@ -0,0 +1,48 @@
+package main
+
+import (
+	"time"
+
+	"github.com/dgrijalva/jwt-go"
+)
+
+func generateTokenPair(user userInfo) (map[string]string, error) {
+	// Create token
+	token := jwt.New(jwt.SigningMethodHS256)
+
+	// Set claims
+	// This is the information which frontend can use
+	// The backend can also decode the token and get admin etc.
+	claims := token.Claims.(jwt.MapClaims)
+	claims["admin"] = false
+	for _, v := range user.Result.Result.MemberofGroup {
+		if v == "admins" {
+			claims["admin"] = true
+		}
+	}
+	claims["sub"] = 1
+	claims["name"] = user.Result.Value
+	claims["exp"] = time.Now().Add(time.Minute * 15).Unix()
+
+	// Generate encoded token and send it as response.
+	// The signing string should be secret (a generated UUID works too)
+	t, err := token.SignedString([]byte("secret"))
+	if err != nil {
+		return nil, err
+	}
+
+	refreshToken := jwt.New(jwt.SigningMethodHS256)
+	rtClaims := refreshToken.Claims.(jwt.MapClaims)
+	rtClaims["sub"] = 1
+	rtClaims["exp"] = time.Now().Add(time.Hour * 24).Unix()
+
+	rt, err := refreshToken.SignedString([]byte("secret"))
+	if err != nil {
+		return nil, err
+	}
+
+	return map[string]string{
+		"access_token":  t,
+		"refresh_token": rt,
+	}, nil
+}