diff --git a/http/http.go b/http/http.go index e6297a8..267d1c4 100644 --- a/http/http.go +++ b/http/http.go @@ -97,6 +97,10 @@ func Run() { http.HandleFunc(ROUTE_ROOT, indexHandler) http.HandleFunc(ROUTE_AUTH, authHandler) + // OAuth2 + LoadOAuth() + LoadServer() + if HttpPortBind > 0 { go runHttp(BindAddressIPv4) if BindAddressIPv4 != "0.0.0.0" { diff --git a/http/oauth.go b/http/oauth.go new file mode 100644 index 0000000..3b64979 --- /dev/null +++ b/http/oauth.go @@ -0,0 +1,148 @@ +package http + +import ( + "encoding/json" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "strings" + + "git.kingpenguin.tk/chteufleur/HTTPAuthentificationOverXMPP.git/xmpp" + + "gopkg.in/oauth2.v3/errors" + "gopkg.in/oauth2.v3/manage" + "gopkg.in/oauth2.v3/models" + "gopkg.in/oauth2.v3/server" + "gopkg.in/oauth2.v3/store" + "gopkg.in/session.v1" +) + +const ( + ROUTE_LOGIN = "/login" + ROUTE_AUTHORIZE = "/authorize" + ROUTE_TOKEN = "/token" +) + +var ( + globalSessions *session.Manager + manager *manage.Manager + clientList []models.Client +) + +func init() { + globalSessions, _ = session.NewManager("memory", `{"cookieName":"gosessionid","gclifetime":3600}`) + go globalSessions.GC() +} + +func LoadConfigClient(configFile string) error { + file, err := ioutil.ReadFile(configFile) + if err != nil { + return err + } + return json.Unmarshal(file, &clientList) +} + +func LoadOAuth() { + manager = manage.NewDefaultManager() + // token store + manager.MustTokenStorage(store.NewMemoryTokenStore()) + + clientStore := store.NewClientStore() + for _, c := range clientList { + clientStore.Set(c.ID, &c) + } + manager.MapClientStorage(clientStore) +} + +func LoadServer() { + srv := server.NewServer(server.NewConfig(), manager) + srv.SetUserAuthorizationHandler(userAuthorizeHandler) + srv.SetInternalErrorHandler(func(err error) (re *errors.Response) { + log.Println("Internal Error:", err.Error()) + return + }) + srv.SetResponseErrorHandler(func(re *errors.Response) { + log.Println("Response Error:", re.Error.Error()) + }) + + http.HandleFunc(ROUTE_LOGIN, loginHandler) + http.HandleFunc(ROUTE_AUTHORIZE, func(w http.ResponseWriter, r *http.Request) { + err := srv.HandleAuthorizeRequest(w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + }) + http.HandleFunc(ROUTE_TOKEN, func(w http.ResponseWriter, r *http.Request) { + err := srv.HandleTokenRequest(w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }) +} + +func userAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string, err error) { + us, err := globalSessions.SessionStart(w, r) + uid := us.Get("UserID") + if uid == nil { + if r.Form == nil { + r.ParseForm() + } + us.Set("Form", r.Form) + w.Header().Set("Location", ROUTE_LOGIN) + w.WriteHeader(http.StatusFound) + return + } + userID = uid.(string) + us.Delete("UserID") + return +} + +func loginHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + us, err := globalSessions.SessionStart(w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + r.ParseForm() + jid := strings.Join(r.Form["jid"], "") + // TODO + answer := xmpp.Confirm(jid, "GET", "toto.fr", "oauth2", -1) + switch answer { + case xmpp.REPLY_OK: + us.Set("LoggedInUserID", jid) + form := us.Get("Form").(url.Values) + u := new(url.URL) + u.Path = ROUTE_AUTHORIZE + u.RawQuery = form.Encode() + w.Header().Set("Location", u.String()) + w.WriteHeader(http.StatusFound) + us.Delete("Form") + us.Set("UserID", us.Get("LoggedInUserID")) + + case xmpp.REPLY_DENY: + w.WriteHeader(http.StatusUnauthorized) + + case xmpp.REPLY_UNREACHABLE: + w.WriteHeader(StatusUnreachable) + + default: + w.WriteHeader(StatusUnknownError) + } + return + } + outputHTML(w, r, "static/login.html") +} + +func outputHTML(w http.ResponseWriter, req *http.Request, filename string) { + file, err := os.Open(filename) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + defer file.Close() + fi, _ := file.Stat() + http.ServeContent(w, req, file.Name(), fi.ModTime(), file) +} diff --git a/main.go b/main.go index 64a9ad5..412cc39 100644 --- a/main.go +++ b/main.go @@ -16,11 +16,13 @@ import ( ) const ( - Version = "v0.5-dev" - configurationFilePath = "http-auth/httpAuth.conf" - langFilePath = "http-auth/messages.lang" - PathConfEnvVariable = "XDG_CONFIG_DIRS" - DefaultXdgConfigDirs = "/etc/xdg" + Version = "v0.5-dev" + configurationDirectory = "http-auth" + configurationFilePath = "httpAuth.conf" + langFilePath = "messages.lang" + oauthClientStoreFilePath = "clientStore.json" + PathConfEnvVariable = "XDG_CONFIG_DIRS" + DefaultXdgConfigDirs = "/etc/xdg" ) var ( @@ -34,6 +36,7 @@ func init() { log.Fatal("Failed to load configuration file.") } loadLangFile() + loadOauthClientStore() // HTTP config httpTimeout, err := strconv.Atoi(mapConfig["http_timeout_sec"]) @@ -78,50 +81,56 @@ func init() { xmpp.VerifyCertValidity = mapConfig["xmpp_verify_cert_validity"] != "false" // Default TRUE } -func loadConfigFile() bool { - ret := false +func loadConfigurationFile(file string) string { + ret := "" envVariable := os.Getenv(PathConfEnvVariable) if envVariable == "" { envVariable = DefaultXdgConfigDirs } for _, path := range strings.Split(envVariable, ":") { - log.Println("Try to find configuration file into " + path) - configFile := path + "/" + configurationFilePath + log.Println("Try to find file (" + file + ") into " + path) + configFile := path + "/" + configurationDirectory + "/" + file if _, err := os.Stat(configFile); err == nil { - // The config file exist - if cfg.Load(configFile, mapConfig) == nil { - // And has been loaded succesfully - log.Println("Find configuration file at " + configFile) - ret = true - break - } + // The file exist + ret = configFile + break } } return ret } +func loadConfigFile() bool { + ret := false + configFile := loadConfigurationFile(configurationFilePath) + if configFile != "" && cfg.Load(configFile, mapConfig) == nil { + // And has been loaded succesfully + log.Println("Find configuration file at " + configFile) + ret = true + } + return ret +} + func loadLangFile() bool { ret := false - envVariable := os.Getenv(PathConfEnvVariable) - if envVariable == "" { - envVariable = DefaultXdgConfigDirs - } - for _, path := range strings.Split(envVariable, ":") { - log.Println("Try to find messages lang file into " + path) - langFile := path + "/" + langFilePath - if _, err := os.Stat(langFile); err == nil { - // The config file exist - if cfg.Load(langFile, xmpp.MapLangs) == nil { - // And has been loaded succesfully - log.Println("Find messages lang file at " + langFile) - ret = true - break - } - } + langFile := loadConfigurationFile(langFilePath) + if cfg.Load(langFile, xmpp.MapLangs) == nil { + // And has been loaded succesfully + log.Println("Find messages lang file at " + langFile) + ret = true } return ret } +func loadOauthClientStore() { + clientStore := loadConfigurationFile(oauthClientStoreFilePath) + err := http.LoadConfigClient(clientStore) + if err == nil { + log.Printf("Find OAuth client store file at " + clientStore) + } else { + log.Printf("Failed to load OAuth client store: ", err) + } +} + func main() { go http.Run() diff --git a/static/login.html b/static/login.html new file mode 100644 index 0000000..ef530db --- /dev/null +++ b/static/login.html @@ -0,0 +1,18 @@ + + + + + HTTP authentication over XMPP + + + +
+

HTTP authentication over XMPP

+
+ + + +
+
+ +