Skip to content

Commit

Permalink
add new authentication system
Browse files Browse the repository at this point in the history
  • Loading branch information
aler9 committed Feb 27, 2024
1 parent d340854 commit 65fd499
Show file tree
Hide file tree
Showing 26 changed files with 980 additions and 544 deletions.
2 changes: 1 addition & 1 deletion apidocs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ components:
type: string
serverCert:
type: string
authMethods:
rtspAuthMethods:
type: array
items:
type: string
Expand Down
216 changes: 216 additions & 0 deletions internal/auth/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package auth

Check warning on line 1 in internal/auth/manager.go

View workflow job for this annotation

GitHub Actions / golangci_lint

package-comments: should have a package comment (revive)

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"regexp"
"strings"
"sync"

"github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/google/uuid"
)

const (
rtspAuthRealm = "IPCAM"
)

// Protocol is a protocol.
type Protocol string

// protocols.
const (
ProtocolRTSP Protocol = "rtsp"
ProtocolRTMP Protocol = "rtmp"
ProtocolHLS Protocol = "hls"
ProtocolWebRTC Protocol = "webrtc"
ProtocolSRT Protocol = "srt"
)

// Request is an authentication request.
type Request struct {
User string
Pass string
IP net.IP
Action conf.AuthAction

// only for ActionPublish and ActionRead
Path string
Protocol Protocol
ID *uuid.UUID
Query string
RTSPRequest *base.Request
RTSPBaseURL *base.URL
RTSPNonce string
}

// Error is a authentication error.
type Error struct {
Message string
}

// Error implements the error interface.
func (e Error) Error() string {
return "authentication failed: " + e.Message

Check warning on line 61 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L60-L61

Added lines #L60 - L61 were not covered by tests
}

func userHasPermission(u *conf.AuthInternalUser, req *Request) bool {
for _, perm := range u.Permissions {
if perm.Action == req.Action {
if perm.Action == conf.AuthActionPublish || perm.Action == conf.AuthActionRead || perm.Action == conf.AuthActionPlayback {

Check failure on line 67 in internal/auth/manager.go

View workflow job for this annotation

GitHub Actions / golangci_lint

line is 125 characters (lll)
if perm.Path == "any" {

Check failure on line 68 in internal/auth/manager.go

View workflow job for this annotation

GitHub Actions / golangci_lint

ifElseChain: rewrite if-else to switch statement (gocritic)
return true

Check warning on line 69 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L69

Added line #L69 was not covered by tests
} else if strings.HasPrefix(perm.Path, "~") {
regexp, err := regexp.Compile(perm.Path[1:])
if err == nil && regexp.MatchString(req.Path) {
return true
}

Check warning on line 74 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L71-L74

Added lines #L71 - L74 were not covered by tests
} else if perm.Path == req.Path {
return true
}
} else {
return true
}

Check warning on line 80 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L78-L80

Added lines #L78 - L80 were not covered by tests
}
}

return false

Check warning on line 84 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L84

Added line #L84 was not covered by tests
}

// Manager is the authentication manager.
type Manager struct {
Method conf.AuthMethod
InternalUsers []conf.AuthInternalUser
HTTPAddress string
RTSPAuthMethods []headers.AuthMethod

mutex sync.RWMutex
}

func (m *Manager) ReloadUsers(u []conf.AuthInternalUser) {

Check warning on line 97 in internal/auth/manager.go

View workflow job for this annotation

GitHub Actions / golangci_lint

exported: exported method Manager.ReloadUsers should have comment or be unexported (revive)
m.mutex.Lock()
defer m.mutex.Unlock()
m.InternalUsers = u

Check warning on line 100 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L97-L100

Added lines #L97 - L100 were not covered by tests
}

func (m *Manager) Authenticate(req *Request) error {

Check warning on line 103 in internal/auth/manager.go

View workflow job for this annotation

GitHub Actions / golangci_lint

exported: exported method Manager.Authenticate should have comment or be unexported (revive)
m.mutex.RLock()
defer m.mutex.RUnlock()

// if this is a RTSP request, fill username and password
var rtspAuthHeader headers.Authorization
if req.RTSPRequest != nil {
err := rtspAuthHeader.Unmarshal(req.RTSPRequest.Header["Authorization"])
if err == nil {
switch rtspAuthHeader.Method {
case headers.AuthBasic:
req.User = rtspAuthHeader.BasicUser
req.Pass = rtspAuthHeader.BasicPass

Check warning on line 115 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L113-L115

Added lines #L113 - L115 were not covered by tests

case headers.AuthDigestMD5:
req.User = rtspAuthHeader.Username

default:
return Error{Message: "unsupported RTSP authentication method"}

Check warning on line 121 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L120-L121

Added lines #L120 - L121 were not covered by tests
}
}
}

if m.Method == conf.AuthMethodInternal {
return m.authenticateInternal(req, &rtspAuthHeader)
}
return m.authenticateHTTP(req)
}

func (m *Manager) authenticateInternal(req *Request, rtspAuthHeader *headers.Authorization) error {
for _, u := range m.InternalUsers {
if err := m.authenticateWithUser(req, rtspAuthHeader, &u); err == nil {
return nil
}
}

return Error{Message: "authentication failed"}

Check warning on line 139 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L139

Added line #L139 was not covered by tests
}

func (m *Manager) authenticateWithUser(
req *Request,
rtspAuthHeader *headers.Authorization,
u *conf.AuthInternalUser,
) error {
if u.User != "any" && !u.User.Check(req.User) {
return Error{Message: "wrong user"}
}

Check warning on line 149 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L148-L149

Added lines #L148 - L149 were not covered by tests

if !u.IPs.Contains(req.IP) {
return Error{Message: "IP not allowed"}
}

Check warning on line 153 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L152-L153

Added lines #L152 - L153 were not covered by tests

if !userHasPermission(u, req) {
return Error{Message: "user doesn't have permission to perform action"}
}

Check warning on line 157 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L156-L157

Added lines #L156 - L157 were not covered by tests

if u.User != "any" {
if req.RTSPRequest != nil && rtspAuthHeader.Method == headers.AuthDigestMD5 {
err := auth.Validate(
req.RTSPRequest,
string(u.User),
string(u.Pass),
req.RTSPBaseURL,
m.RTSPAuthMethods,
rtspAuthRealm,
req.RTSPNonce)
if err != nil {
return Error{Message: err.Error()}
}

Check warning on line 171 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L170-L171

Added lines #L170 - L171 were not covered by tests
} else if !u.Pass.Check(req.Pass) {
return Error{Message: "invalid credentials"}
}

Check warning on line 174 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L173-L174

Added lines #L173 - L174 were not covered by tests
}

return nil
}

func (m *Manager) authenticateHTTP(req *Request) error {
enc, _ := json.Marshal(struct {
IP string `json:"ip"`
User string `json:"user"`
Password string `json:"password"`
Action string `json:"action"`
Path string `json:"path"`
Protocol string `json:"protocol"`
ID *uuid.UUID `json:"id"`
Query string `json:"query"`
}{
IP: req.IP.String(),
User: req.User,
Password: req.Pass,
Action: string(req.Action),
Path: req.Path,
Protocol: string(req.Protocol),
ID: req.ID,
Query: req.Query,
})

res, err := http.Post(m.HTTPAddress, "application/json", bytes.NewReader(enc))
if err != nil {
return Error{Message: fmt.Sprintf("HTTP request failed: %v", err)}
}

Check warning on line 204 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L203-L204

Added lines #L203 - L204 were not covered by tests
defer res.Body.Close()

if res.StatusCode < 200 || res.StatusCode > 299 {
if resBody, err := io.ReadAll(res.Body); err == nil && len(resBody) != 0 {
return Error{Message: fmt.Sprintf("server replied with code %d: %s", res.StatusCode, string(resBody))}
}

Check warning on line 210 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L208-L210

Added lines #L208 - L210 were not covered by tests

return Error{Message: fmt.Sprintf("server replied with code %d", res.StatusCode)}

Check warning on line 212 in internal/auth/manager.go

View check run for this annotation

Codecov / codecov/patch

internal/auth/manager.go#L212

Added line #L212 was not covered by tests
}

return nil
}

0 comments on commit 65fd499

Please sign in to comment.