mirror of
https://github.com/fumiama/go-nd-portal.git
synced 2026-07-02 09:00:25 +08:00
feat: support new dorm area login (#1)
## Pull Request Overview
This PR enhances the login CLI to support a new “dorm area” and replaces the old `-x` flag with two new flags for server host (`-s`) and login type (`-t`).
- Introduces a `LoginType` enum and dynamic domain/AcID mapping in `Portal`.
- Refactors URL construction to use `go-querystring` and JSON-encoded user info.
- Updates tests and removes the hard-coded `-x` logic in `cmd/main.go`.
### Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
<details>
<summary>Show a summary per file</summary>
| File | Description |
| ---------------------- | --------------------------------------------------------------- |
| portal/server_test.go | Updated tests to supply the new `loginType` parameter and use `GetUserInfo`. |
| portal/server.go | Reworked URL builders (`GetChallengeURL`, `GetLoginURL`), JSON `UserInfo`, and dynamic flags. |
| portal/portal.go | Added `LoginType`, `ToDomainAcID`, updated `NewPortal`, `GetChallenge`, and `Login` signatures. |
| cmd/main.go | Removed `-x`, added `-s`/`-t` flags, wired them through to `Portal`. |
| base64/base64.go | Fixed typo in package comment. |
| go.mod | Added `github.com/google/go-querystring` dependency. |
</details>
<details>
<summary>Comments suppressed due to low confidence (4)</summary>
**base64/base64.go:1**
* Typo in the comment: 'talbe' should be 'table'.
```
// Package base64 with customized talbe
```
**portal/portal.go:53**
* New login types (`qsh-dx`, `qshd-dx`, `qshd-cmcc`) were added but there are no unit tests verifying `ToDomainAcID` returns the correct domain and AC ID for each, or that invalid types produce an error. Consider adding tests for each mapping and an invalid case.
```
func (lt LoginType) ToDomainAcID() (string, string, error) {
```
**cmd/main.go:54**
* [nitpick] The flag variables `s` and `t` are not descriptive. Consider renaming them to `server` and `loginType` (or similar) to improve clarity for users and maintainers.
```
s := flag.String("s", portal.PortalServerIPQsh, "login host")
```
**cmd/main.go:106**
* The call to `netip.ParseAddr` requires importing the `net/netip` package, but `netip` is not imported. Add `import "net/netip"` to fix the compile error.
```
_, err := netip.ParseAddr(*s)
```
</details>
---------
Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com>
This commit is contained in:
130
portal/portal.go
130
portal/portal.go
@@ -1,3 +1,4 @@
|
||||
// Package portal handles login process
|
||||
package portal
|
||||
|
||||
import (
|
||||
@@ -6,9 +7,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -17,35 +16,107 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrIllegalIPv4 is returned when an invalid IPv4 address is provided
|
||||
ErrIllegalIPv4 = errors.New("illegal ipv4")
|
||||
// ErrIllegalLoginType is returned when an invalid login type is provided
|
||||
ErrIllegalLoginType = errors.New("illegal login type")
|
||||
// ErrUnexpectedChallengeResponse is returned when challenge is shorter than expected
|
||||
ErrUnexpectedChallengeResponse = errors.New("unexpected challenge response")
|
||||
// ErrUnexpectedLoginResponse is returned when login resp is shorter than expected
|
||||
ErrUnexpectedLoginResponse = errors.New("unexpected login response")
|
||||
)
|
||||
|
||||
// Portal struct for login config
|
||||
type Portal struct {
|
||||
nam string
|
||||
pwd string
|
||||
ip net.IP
|
||||
name string
|
||||
pswd string
|
||||
ip net.IP
|
||||
domain string
|
||||
acid string
|
||||
}
|
||||
|
||||
// LoginType defines known login types
|
||||
type LoginType string
|
||||
|
||||
const (
|
||||
// LoginTypeQshEdu edu in Qsh work area
|
||||
LoginTypeQshEdu LoginType = "qsh-edu"
|
||||
// LoginTypeQshDX dx in Qsh work area
|
||||
LoginTypeQshDX LoginType = "qsh-dx"
|
||||
// LoginTypeQshDormDX dx in Qsh new dorm area
|
||||
LoginTypeQshDormDX LoginType = "qshd-dx"
|
||||
// LoginTypeQshDormCMCC cmcc in Qsh new dorm area
|
||||
LoginTypeQshDormCMCC LoginType = "qshd-cmcc"
|
||||
)
|
||||
|
||||
// ToDomainAcID converts LoginType to domain and acid
|
||||
func (lt LoginType) ToDomainAcID() (string, string, error) {
|
||||
var domain, acid string
|
||||
switch lt {
|
||||
case LoginTypeQshEdu:
|
||||
// qsh-edu is assumed that cant login from dorm
|
||||
domain = PortalDomainQsh
|
||||
acid = AcIDQsh
|
||||
case LoginTypeQshDX:
|
||||
domain = PortalDomainQshDX
|
||||
acid = AcIDQsh
|
||||
case LoginTypeQshDormDX:
|
||||
domain = PortalDomainQshDX
|
||||
acid = AcIDQshDorm
|
||||
case LoginTypeQshDormCMCC:
|
||||
domain = PortalDomainQshCMCC
|
||||
acid = AcIDQshDorm
|
||||
default:
|
||||
return "", "", ErrIllegalLoginType
|
||||
}
|
||||
|
||||
return domain, acid, nil
|
||||
}
|
||||
|
||||
// rsp struct for converting from raw response data to JSON
|
||||
type rsp struct {
|
||||
Challenge string `json:"challenge"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func NewPortal(name, password string, ipv4 net.IP) (*Portal, error) {
|
||||
// NewPortal creates a new Portal instance
|
||||
func NewPortal(name, password string, ipv4 net.IP, loginType LoginType) (*Portal, error) {
|
||||
if len(ipv4) != 4 {
|
||||
return nil, ErrIllegalIPv4
|
||||
}
|
||||
|
||||
domain, acid, err := loginType.ToDomainAcID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debugf("portal domain: %s, ac_id: %s", domain, acid)
|
||||
|
||||
return &Portal{
|
||||
nam: name,
|
||||
pwd: password,
|
||||
ip: ipv4,
|
||||
name: name,
|
||||
pswd: password,
|
||||
ip: ipv4,
|
||||
domain: domain,
|
||||
acid: acid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Portal) GetChallenge(u string) (string, error) {
|
||||
u = fmt.Sprintf(u, "gondportal", url.QueryEscape(p.nam), p.ip, time.Now().UnixMilli())
|
||||
// GetChallenge gets token for encryption from server
|
||||
// input:
|
||||
// server IP
|
||||
func (p *Portal) GetChallenge(sIP string) (string, error) {
|
||||
// Note: no need to do URL encoding here
|
||||
u, err := GetChallengeURL(
|
||||
sIP,
|
||||
"gondportal",
|
||||
p.name,
|
||||
p.domain,
|
||||
p.ip,
|
||||
time.Now().UnixMilli(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
logrus.Debugln("GET", u)
|
||||
data, err := requestDataWith(u, "GET", PortalHeaderUA)
|
||||
if err != nil {
|
||||
@@ -67,17 +138,42 @@ func (p *Portal) GetChallenge(u string) (string, error) {
|
||||
return r.Challenge, nil
|
||||
}
|
||||
|
||||
// PasswordHMd5 encrypts password with hmacmd5 algorithm
|
||||
func (p *Portal) PasswordHMd5(challenge string) string {
|
||||
var buf [16]byte
|
||||
h := hmac.New(md5.New, helper.StringToBytes(challenge))
|
||||
_, _ = h.Write(helper.StringToBytes(p.pwd))
|
||||
_, _ = h.Write(helper.StringToBytes(p.pswd))
|
||||
return hex.EncodeToString(h.Sum(buf[:0]))
|
||||
}
|
||||
|
||||
func (p *Portal) Login(u, domain, challenge string) error {
|
||||
info := EncodeUserInfo(p.String(), challenge)
|
||||
// Login sends login request to server
|
||||
// input:
|
||||
// server IP
|
||||
// challenge
|
||||
func (p *Portal) Login(sIP, challenge string) error {
|
||||
userInfo, err := GetUserInfo(p.name, p.domain, p.pswd, p.ip, p.acid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info := EncodeUserInfo(userInfo, challenge)
|
||||
hmd5 := p.PasswordHMd5(challenge)
|
||||
u = fmt.Sprintf(u, "gondportal", url.QueryEscape(p.nam), hmd5, p.ip, p.CheckSum(domain, challenge, hmd5, info), url.QueryEscape(info), time.Now().UnixMilli())
|
||||
// Note: no need to do URL encoding here
|
||||
u, err := GetLoginURL(
|
||||
sIP,
|
||||
"gondportal",
|
||||
p.name,
|
||||
p.domain,
|
||||
hmd5,
|
||||
p.acid,
|
||||
p.ip,
|
||||
p.CheckSum(challenge, p.name, p.domain, hmd5, p.acid, p.ip, info),
|
||||
info,
|
||||
time.Now().UnixMilli(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debugln("GET", u)
|
||||
data, err := requestDataWith(u, "GET", PortalHeaderUA)
|
||||
if err != nil {
|
||||
@@ -98,7 +194,3 @@ func (p *Portal) Login(u, domain, challenge string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Portal) String() string {
|
||||
return fmt.Sprintf(PortalUserInfo, p.nam, p.pwd, p.ip)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user