commit b7bea8cf094de7672a66c141892f595d5b613aa4 Author: Marcel Transier Date: Sat Oct 5 20:32:39 2019 +0200 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c1cd01 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# LSF - LSF S... F... rekusives akronym!!!111elf + +## TODO + +- documentation +- tests +- webapi diff --git a/cmd/lsf/main.go b/cmd/lsf/main.go new file mode 100644 index 0000000..2a5f283 --- /dev/null +++ b/cmd/lsf/main.go @@ -0,0 +1,137 @@ +// Command lsf provides functions to use the lsf with the commandline. +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "path" + "strings" + "syscall" + + "git.marceltransier.de/lsf" + "github.com/pkg/errors" + "golang.org/x/crypto/ssh/terminal" +) + +var ( + sessionCache string + username string +) + +func init() { + homeDir, err := os.UserHomeDir() + if err != nil { + log.Fatal(err) + } + defaultCacheDir := path.Join(homeDir, ".cache/go-lsf/sessions") + flag.StringVar(&sessionCache, "session-cache", defaultCacheDir, "path where the session tokens are located") + flag.StringVar(&username, "username", "", "username to login with in lsf") + flag.Parse() + + err = os.MkdirAll(sessionCache, os.ModePerm) + if err != nil { + log.Fatal(err) + } +} + +func main() { + if len(username) == 0 { + var err error + username, err = readUsername() + if err != nil { + log.Fatal(err) + } + } + + s, err := session(username) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("s: %+v\n", s) +} + +func readUsername() (string, error) { + var username string + fmt.Print("Username: ") + _, err := fmt.Scanf("%s", &username) + if err != nil { + return "", errors.Wrap(err, "could not read username") + } + return username, nil +} + +func readPassword() (string, error) { + var password string + fmt.Print("Password: ") + b, err := terminal.ReadPassword(int(syscall.Stdin)) + if err != nil { + return "", errors.Wrap(err, "could not read password") + } + fmt.Print("\n") + password = string(b) + return password, nil +} + +func session(username string) (*lsf.Session, error) { + sessionPath := path.Join(sessionCache, username) + sessionFile, err := os.Open(sessionPath) + if err != nil { + pErr, ok := err.(*os.PathError) + if !ok { + return nil, err + } + errno, ok := pErr.Unwrap().(syscall.Errno) + if !ok { + return nil, err + } + if errno != syscall.ENOENT { + log.Fatal(err) + } + log.Printf("no session for %s cached\n", username) + return login(username) + } + defer sessionFile.Close() + + b, err := ioutil.ReadAll(sessionFile) + if err != nil { + return nil, errors.Wrapf(err, "could not read session file %s", sessionPath) + } + sid := strings.TrimSuffix(string(b), "\n") + s := &lsf.Session{ + SID: sid, + } + valid, err := s.Valid() + if err != nil { + return nil, errors.Wrap(err, "could not check session") + } + if !valid { + fmt.Println("session is not valid") + err := os.Remove(sessionPath) + if err != nil { + log.Println(errors.Wrap(err, "could not remove invalid session file")) + } + return login(username) + } + return s, nil +} + +func login(username string) (*lsf.Session, error) { + password, err := readPassword() + if err != nil { + return nil, errors.Wrap(err, "could not login") + } + s, err := lsf.Login(username, password) + if err != nil { + return nil, err + } + sessionPath := path.Join(sessionCache, username) + err = ioutil.WriteFile(sessionPath, []byte(s.SID), 0666) + if err != nil { + log.Println(errors.Wrap(err, "could not cache session id")) + } + return s, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d1e0936 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module git.marceltransier.de/lsf + +go 1.13 + +require ( + github.com/PuerkitoBio/goquery v1.5.0 + github.com/pkg/errors v0.8.1 + golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cb780ed --- /dev/null +++ b/go.sum @@ -0,0 +1,18 @@ +github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= +github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= +github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= +github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/module.go b/module.go new file mode 100644 index 0000000..7c2c9e6 --- /dev/null +++ b/module.go @@ -0,0 +1,5 @@ +package lsf + +type Modul struct { + Nr int +} diff --git a/noten.go b/noten.go new file mode 100644 index 0000000..def852f --- /dev/null +++ b/noten.go @@ -0,0 +1,10 @@ +package lsf + +import ( + "github.com/pkg/errors" + //"github.com/PuerkitoBio/goquery" +) + +func (s *Session) Noten() (map[Modul]float32, error) { + return nil, errors.New("noten not implemented yet") +} diff --git a/session.go b/session.go new file mode 100644 index 0000000..68a8e1a --- /dev/null +++ b/session.go @@ -0,0 +1,75 @@ +package lsf + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/pkg/errors" +) + +type Session struct { + SID string +} + +func (s *Session) Valid() (bool, error) { + client := &http.Client{} + req, err := http.NewRequest("GET", "https://lsf.hs-worms.de/qisserver/rds?state=user&type=8&topitem=functions&breadCrumbSource=portal", nil) + if err != nil { + return false, errors.Wrap(err, "could not prepare the request") + } + req.Header.Add("Cookie", fmt.Sprintf("JSESSIONID=%s", s.SID)) + resp, err := client.Do(req) + if err != nil { + return false, errors.Wrap(err, "could not do the request") + } + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return false, errors.Wrap(err, "could not read the response body") + } + + if strings.Contains(string(b), "Logout") { + return true, nil + } + if strings.Contains(string(b), "Login") { + return false, nil + } + return false, errors.New("unexpected response body") +} + +func Login(username, password string) (*Session, error) { + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { // don't follow redirects + return http.ErrUseLastResponse + }, + } + form := url.Values{} + form.Add("asdf", username) // who wrote this backend? lol + form.Add("fdsa", password) + form.Add("submit", "Login") + req, err := http.NewRequest("POST", "https://lsf.hs-worms.de/qisserver/rds?state=user&type=1&category=auth.login&startpage=portal.vm&breadCrumbSource=portal", strings.NewReader(form.Encode())) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + if err != nil { + return nil, errors.Wrap(err, "could not prepare the login request") + } + resp, err := client.Do(req) + if err != nil { + return nil, errors.Wrap(err, "could not do the login request") + } + if resp.StatusCode == 302 { + for _, c := range resp.Cookies() { + if c.Name == "JSESSIONID" { + return &Session{ + SID: c.Value, + }, nil + } + } + return nil, errors.New("no session cookie found") + } + if resp.StatusCode == 200 { + return nil, errors.New("wrong credentials") // TODO or other errors + } + return nil, errors.New("unexpected response status code") +}