Parcourir la source

OAuth implementation part 2

Now stores everything in the database
AY il y a 4 ans
Parent
commit
2e601cf6cc
3 fichiers modifiés avec 331 ajouts et 17 suppressions
  1. 121 15
      mod/auth/oauth2/oauth2.go
  2. 23 2
      oauth.go
  3. 187 0
      web/SystemAO/advance/oauth.html

+ 121 - 15
mod/auth/oauth2/oauth2.go

@@ -5,6 +5,7 @@ import (
 	"io/ioutil"
 	"log"
 	"net/http"
+	"strconv"
 	"time"
 
 	"golang.org/x/oauth2"
@@ -12,6 +13,7 @@ import (
 	auth "imuslab.com/arozos/mod/auth"
 	syncdb "imuslab.com/arozos/mod/auth/oauth2/syncdb"
 	reg "imuslab.com/arozos/mod/auth/register"
+	db "imuslab.com/arozos/mod/database"
 )
 
 type OauthHandler struct {
@@ -21,6 +23,8 @@ type OauthHandler struct {
 	DefaultUserGroup  string
 	ag                *auth.AuthAgent
 	reg               *reg.RegisterHandler
+	coredb            *db.Database
+	config            *Config
 }
 
 type GoogleField struct {
@@ -34,22 +38,37 @@ type GoogleField struct {
 	Locale        string `json:"locale"`
 }
 
+type Config struct {
+	Enabled          bool   `json:"enabled"`
+	IDP              string `json:"idp"`
+	RedirectURL      string `json:"redirect_url"`
+	ClientID         string `json:"client_id"`
+	ClientSecret     string `json:"client_secret"`
+	DefaultUserGroup string `json:"default_user_group"`
+}
+
 //NewOauthHandler xxx
-func NewOauthHandler(authAgent *auth.AuthAgent, register *reg.RegisterHandler) *OauthHandler {
+func NewOauthHandler(authAgent *auth.AuthAgent, register *reg.RegisterHandler, coreDb *db.Database) *OauthHandler {
+	err := coreDb.NewTable("oauth")
+	if err != nil {
+		log.Println("Failed to create oauth database. Terminating.")
+		panic(err)
+	}
+
 	NewlyCreatedOauthHandler := OauthHandler{
 		googleOauthConfig: &oauth2.Config{
-			RedirectURL:  "http://localhost:8080/system/auth/oauth/authorize",
-			ClientID:     "682431817920-nkmfn7m6uq0qbdo00hr2944m6r3hj8ua.apps.googleusercontent.com",
-			ClientSecret: "Obdlr2S5n8rj_qwsPLhToD3h",
+			RedirectURL:  readSingleConfig("redirecturl", coreDb) + "/system/auth/oauth/authorize",
+			ClientID:     readSingleConfig("clientid", coreDb),
+			ClientSecret: readSingleConfig("clientsecret", coreDb),
 			Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile",
 				"https://www.googleapis.com/auth/userinfo.email"},
 			Endpoint: google.Endpoint,
 		},
-		// Some random string, random for each request
-		DefaultUserGroup: "default",
+		DefaultUserGroup: readSingleConfig("defaultusergroup", coreDb),
 		ag:               authAgent,
 		syncDb:           syncdb.NewSyncDB(),
 		reg:              register,
+		coredb:           coreDb,
 	}
 
 	return &NewlyCreatedOauthHandler
@@ -76,22 +95,19 @@ func (oh *OauthHandler) HandleAuthorize(w http.ResponseWriter, r *http.Request)
 	//read the uuid(aka the state parameter)
 	uuid, err := r.Cookie("uuid_login")
 	if err != nil {
-		w.Header().Set("Content-Type", "text/html; charset=UTF-8")
-		w.Write([]byte("Invalid redirect URI."))
+		sendTextResponse(w, "Invalid redirect URI.")
 	}
 
 	state := r.FormValue("state")
 	if state != uuid.Value {
-		w.Header().Set("Content-Type", "text/html; charset=UTF-8")
-		w.Write([]byte("Invalid oauth state."))
+		sendTextResponse(w, "Invalid oauth state.")
 		return
 	}
 
 	code := r.FormValue("code")
 	token, err := oh.googleOauthConfig.Exchange(oauth2.NoContext, code)
 	if err != nil {
-		w.Header().Set("Content-Type", "text/html; charset=UTF-8")
-		w.Write([]byte("Code exchange failed."))
+		sendTextResponse(w, "Code exchange failed.")
 		return
 	}
 
@@ -108,8 +124,7 @@ func (oh *OauthHandler) HandleAuthorize(w http.ResponseWriter, r *http.Request)
 		if oh.reg.AllowRegistry {
 			http.Redirect(w, r, "/public/register/register.system?user="+data.Email, 302)
 		} else {
-			w.Header().Set("Content-Type", "text/html; charset=UTF-8")
-			w.Write([]byte("You are not allowed to register in this system.&nbsp;<a href=\"/\">Back</a>"))
+			sendTextResponse(w, "You are not allowed to register in this system.&nbsp;<a href=\"/\">Back</a>")
 		}
 	} else {
 		log.Println(data.Email + " logged in via OAuth.")
@@ -125,7 +140,11 @@ func (oh *OauthHandler) HandleAuthorize(w http.ResponseWriter, r *http.Request)
 }
 
 func (oh *OauthHandler) CheckOAuth(w http.ResponseWriter, r *http.Request) {
-	sendJSONResponse(w, "true")
+	enabled := oh.readSingleConfig("enabled")
+	if enabled == "" {
+		enabled = "false"
+	}
+	sendJSONResponse(w, enabled)
 }
 
 //https://golangcode.com/add-a-http-cookie/
@@ -138,3 +157,90 @@ func (oh *OauthHandler) addCookie(w http.ResponseWriter, name, value string, ttl
 	}
 	http.SetCookie(w, &cookie)
 }
+
+func (oh *OauthHandler) ReadConfig(w http.ResponseWriter, r *http.Request) {
+	enabled, _ := strconv.ParseBool(oh.readSingleConfig("enabled"))
+	idp := oh.readSingleConfig("idp")
+	redirecturl := oh.readSingleConfig("redirecturl")
+	clientid := oh.readSingleConfig("clientid")
+	clientsecret := oh.readSingleConfig("clientsecret")
+	defaultusergroup := oh.readSingleConfig("defaultusergroup")
+
+	config, err := json.Marshal(Config{
+		Enabled:          enabled,
+		IDP:              idp,
+		RedirectURL:      redirecturl,
+		ClientID:         clientid,
+		ClientSecret:     clientsecret,
+		DefaultUserGroup: defaultusergroup,
+	})
+	if err != nil {
+		empty, _ := json.Marshal(Config{})
+		sendJSONResponse(w, string(empty))
+	}
+	sendJSONResponse(w, string(config))
+}
+
+func (oh *OauthHandler) WriteConfig(w http.ResponseWriter, r *http.Request) {
+	enabled, err := mv(r, "enabled", true)
+	if err != nil {
+		sendTextResponse(w, "enabled field can't be empty'")
+	}
+	idp, err := mv(r, "idp", true)
+	if err != nil {
+		sendTextResponse(w, "idp field can't be empty'")
+	}
+	redirecturl, err := mv(r, "redirecturl", true)
+	if err != nil {
+		sendTextResponse(w, "redirecturl field can't be empty'")
+	}
+	clientid, err := mv(r, "clientid", true)
+	if err != nil {
+		sendTextResponse(w, "clientid field can't be empty'")
+	}
+	clientsecret, err := mv(r, "clientsecret", true)
+	if err != nil {
+		sendTextResponse(w, "clientsecret field can't be empty'")
+	}
+	defaultusergroup, err := mv(r, "defaultusergroup", true)
+	if err != nil {
+		sendTextResponse(w, "defaultusergroup field can't be empty'")
+	}
+
+	oh.coredb.Write("oauth", "enabled", enabled)
+	oh.coredb.Write("oauth", "idp", idp)
+	oh.coredb.Write("oauth", "redirecturl", redirecturl)
+	oh.coredb.Write("oauth", "clientid", clientid)
+	oh.coredb.Write("oauth", "clientsecret", clientsecret)
+	oh.coredb.Write("oauth", "defaultusergroup", defaultusergroup)
+
+	//update the information inside the oauth class
+	oh.googleOauthConfig = &oauth2.Config{
+		RedirectURL:  oh.readSingleConfig("redirecturl") + "/system/auth/oauth/authorize",
+		ClientID:     oh.readSingleConfig("clientid"),
+		ClientSecret: oh.readSingleConfig("clientsecret"),
+		Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile",
+			"https://www.googleapis.com/auth/userinfo.email"},
+		Endpoint: google.Endpoint,
+	}
+
+	sendOK(w)
+}
+
+func (oh *OauthHandler) readSingleConfig(key string) string {
+	var value string
+	err := oh.coredb.Read("oauth", key, &value)
+	if err != nil {
+		value = ""
+	}
+	return value
+}
+
+func readSingleConfig(key string, coredb *db.Database) string {
+	var value string
+	err := coredb.Read("oauth", key, &value)
+	if err != nil {
+		value = ""
+	}
+	return value
+}

+ 23 - 2
oauth.go

@@ -4,12 +4,33 @@ import (
 	"net/http"
 
 	oauth "imuslab.com/arozos/mod/auth/oauth2"
+	prout "imuslab.com/arozos/mod/prouter"
 )
 
 func OAuthInit() {
-	//OAuth test
-	oAuthHandler := oauth.NewOauthHandler(authAgent, registerHandler)
+	oAuthHandler := oauth.NewOauthHandler(authAgent, registerHandler, sysdb)
+
+	adminRouter := prout.NewModuleRouter(prout.RouterOption{
+		ModuleName:  "System Setting",
+		AdminOnly:   true,
+		UserHandler: userHandler,
+		DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
+			errorHandlePermissionDenied(w, r)
+		},
+	})
+
 	http.HandleFunc("/system/auth/oauth/login", oAuthHandler.HandleLogin)
 	http.HandleFunc("/system/auth/oauth/authorize", oAuthHandler.HandleAuthorize)
 	http.HandleFunc("/system/auth/oauth/checkoauth", oAuthHandler.CheckOAuth)
+	adminRouter.HandleFunc("/system/auth/oauth/config/read", oAuthHandler.ReadConfig)
+	adminRouter.HandleFunc("/system/auth/oauth/config/write", oAuthHandler.WriteConfig)
+
+	registerSetting(settingModule{
+		Name:         "OAuth",
+		Desc:         "Allows external account access to system",
+		IconPath:     "SystemAO/advance/img/small_icon.png",
+		Group:        "Advance",
+		StartDir:     "SystemAO/advance/oauth.html",
+		RequireAdmin: true,
+	})
 }

+ 187 - 0
web/SystemAO/advance/oauth.html

@@ -0,0 +1,187 @@
+<html>
+
+<head>
+    <title>OAuth Login</title>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
+    <link rel="stylesheet" href="../../script/semantic/semantic.css">
+    <script type="application/javascript" src="../../script/jquery.min.js"></script>
+    <script type="application/javascript" src="../../script/clipboard.min.js"></script>
+    <script type="application/javascript" src="../../script/semantic/semantic.js"></script>
+    <style>
+        /* Tooltip container */
+        
+        .tooltip {
+            position: relative;
+            display: inline-block;
+            border-bottom: 1px dotted black;
+            /* If you want dots under the hoverable text */
+        }
+        /* Tooltip text */
+        
+        .tooltip .tooltiptext {
+            visibility: hidden;
+            width: 120px;
+            background-color: #555;
+            color: #fff;
+            text-align: center;
+            padding: 5px 0;
+            border-radius: 6px;
+            /* Position the tooltip text */
+            position: absolute;
+            z-index: 1;
+            bottom: 125%;
+            left: 50%;
+            margin-left: -60px;
+            /* Fade in tooltip */
+            opacity: 0;
+            transition: opacity 0.3s;
+        }
+        /* Tooltip arrow */
+        
+        .tooltip .tooltiptext::after {
+            content: "";
+            position: absolute;
+            top: 100%;
+            left: 50%;
+            margin-left: -5px;
+            border-width: 5px;
+            border-style: solid;
+            border-color: #555 transparent transparent transparent;
+        }
+    </style>
+</head>
+
+<body>
+    <div class="ui container">
+        <div class="ui basic segment">
+            <div class="ui header">
+                <i class="tv icon"></i>
+                <div class="content">
+                    OAuth
+                    <div class="sub header">Allow external account access to the services</div>
+                </div>
+            </div>
+        </div>
+        <div class="ui divider"></div>
+        <div class="ui form">
+            <div class="field">
+                <div class="ui toggle checkbox">
+                    <input type="checkbox" id="enable" name="public">
+                    <label>Enable OAuth</label>
+                </div>
+            </div>
+            <div class="field">
+                <label>Select OAuth IdP (aka Service provider)</label>
+                <div class="ui selection fluid dropdown" autocomplete="false">
+                    <input type="hidden" id="idp" name="idp" autocomplete="false">
+                    <i class="dropdown icon"></i>
+                    <div class="default text">Select service provider</div>
+                    <div id="idplist" class="menu">
+
+                    </div>
+                </div>
+            </div>
+            <div class="field">
+                <label>Redirect URL (you have to provide https://YOUR_DOMAIN/system/auth/oauth/authorize at the 3rd auth rely)</label>
+                <div class="ui fluid input">
+                    <input type="text" id="redirecturl" placeholder="https://YOUR_DOMAIN/">
+                </div>
+            </div>
+            <div class="field">
+                <label>Client ID</label>
+                <div class="ui fluid input">
+                    <input type="text" id="clientid" placeholder="Client ID">
+                </div>
+            </div>
+            <div class="field">
+                <label>Client Secret</label>
+                <div class="ui fluid input">
+                    <input type="text" id="clientsecret" placeholder="Client Secret">
+                </div>
+            </div>
+            <div class="field">
+                <label>Default user group</label>
+                <div class="ui selection fluid dropdown" autocomplete="false">
+                    <input type="hidden" id="group" name="group" autocomplete="false">
+                    <i class="dropdown icon"></i>
+                    <div class="default text">Select user group</div>
+                    <div id="grouplist" class="menu">
+
+                    </div>
+                </div>
+            </div>
+            <button id="ntb" onclick="update();" class="ui green button" type="submit">Update</button>
+        </div>
+        <div class="ui divider"></div>
+        <br><br>
+    </div>
+
+
+    <script>
+        $(document).ready(function() {
+            loadIdpList();
+            loadGroupList();
+            read();
+        });
+
+        function read() {
+            $.getJSON("../../system/auth/oauth/config/read", function(data) {
+                if (data.enabled) {
+                    $("#enable").parent().checkbox("check")
+                }
+                $("#idp").parent().dropdown("set selected", data.idp);
+                $("#redirecturl").val(data.redirect_url);
+                $("#clientid").val(data.client_id);
+                $("#clientsecret").val(data.client_secret);
+                $("#group").parent().dropdown("set selected", data.default_user_group);
+            });
+        }
+
+        function update() {
+            $.post("../../system/auth/oauth/config/write", {
+                    enabled: $("#enable").parent().checkbox("is checked"),
+                    idp: $("#idp").val(),
+                    redirecturl: $("#redirecturl").val(),
+                    clientid: $("#clientid").val(),
+                    clientsecret: $("#clientsecret").val(),
+                    defaultusergroup: $("#group").val()
+                })
+                .done(function(data) {
+                    alert(data);
+                });
+        }
+
+        function loadIdpList() {
+            $("#idplist").html("");
+            //var data = ["Google", "Microsoft", "Github"];
+            var data = ["Google"];
+            if (data.error !== undefined) {
+                alert(data.error);
+            } else {
+                for (var i = 0; i < data.length; i++) {
+                    let idpinfo = data[i];
+                    $("#idplist").append(`<div class="item" data-value="${idpinfo}">${idpinfo}</div>`);
+                }
+            }
+            $("#idplist").parent().dropdown();
+        }
+
+        function loadGroupList() {
+            $("#grouplist").html("");
+            $.get("../../system/permission/listgroup", function(data) {
+                if (data.error !== undefined) {
+                    alert(data.error);
+                } else {
+                    for (var i = 0; i < data.length; i++) {
+                        let groupinfo = data[i];
+                        $("#grouplist").append(`<div class="item" data-value="${groupinfo}">${groupinfo}</div>`);
+                    }
+                }
+                $("#grouplist").parent().dropdown();
+            });
+        }
+    </script>
+</body>
+
+</html>