You've already forked oauth2-proxy
mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2025-06-15 00:15:00 +02:00
Initialise Session Storage in NewOAuthProxy instead of validation
This commit is contained in:
@ -22,6 +22,7 @@ import (
|
||||
"github.com/mbland/hmacauth"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
|
||||
@ -35,7 +36,7 @@ const (
|
||||
// The rawCookieSecret is 32 bytes and the base64CookieSecret is the base64
|
||||
// encoded version of this.
|
||||
rawCookieSecret = "secretthirtytwobytes+abcdefghijk"
|
||||
base64CookieSecret = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpamsK"
|
||||
base64CookieSecret = "c2VjcmV0dGhpcnR5dHdvYnl0ZXMrYWJjZGVmZ2hpams"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -82,10 +83,10 @@ func TestWebSocketProxy(t *testing.T) {
|
||||
|
||||
backendURL, _ := url.Parse(backend.URL)
|
||||
|
||||
options := options.NewOptions()
|
||||
opts := baseTestOptions()
|
||||
var auth hmacauth.HmacAuth
|
||||
options.PassHostHeader = true
|
||||
proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, options, auth)
|
||||
opts.PassHostHeader = true
|
||||
proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, opts, auth)
|
||||
frontend := httptest.NewServer(proxyHandler)
|
||||
defer frontend.Close()
|
||||
|
||||
@ -172,13 +173,14 @@ func TestEncodedSlashes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRobotsTxt(t *testing.T) {
|
||||
opts := options.NewOptions()
|
||||
opts := baseTestOptions()
|
||||
opts.ClientID = "asdlkjx"
|
||||
opts.ClientSecret = "alkgks"
|
||||
opts.Cookie.Secret = rawCookieSecret
|
||||
validation.Validate(opts)
|
||||
|
||||
proxy := NewOAuthProxy(opts, func(string) bool { return true })
|
||||
proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
|
||||
assert.NoError(t, err)
|
||||
rw := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/robots.txt", nil)
|
||||
proxy.ServeHTTP(rw, req)
|
||||
@ -187,7 +189,7 @@ func TestRobotsTxt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsValidRedirect(t *testing.T) {
|
||||
opts := options.NewOptions()
|
||||
opts := baseTestOptions()
|
||||
opts.ClientID = "skdlfj"
|
||||
opts.ClientSecret = "fgkdsgj"
|
||||
opts.Cookie.Secret = base64CookieSecret
|
||||
@ -202,7 +204,8 @@ func TestIsValidRedirect(t *testing.T) {
|
||||
}
|
||||
validation.Validate(opts)
|
||||
|
||||
proxy := NewOAuthProxy(opts, func(string) bool { return true })
|
||||
proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
|
||||
assert.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
Desc, Redirect string
|
||||
@ -453,11 +456,10 @@ func TestOpenRedirects(t *testing.T) {
|
||||
"www.whitelisteddomain.tld",
|
||||
}
|
||||
err := validation.Validate(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
proxy := NewOAuthProxy(opts, func(string) bool { return true })
|
||||
proxy, err := NewOAuthProxy(opts, func(string) bool { return true })
|
||||
assert.NoError(t, err)
|
||||
|
||||
file, err := os.Open("./test/openredirects.txt")
|
||||
if err != nil {
|
||||
@ -545,7 +547,7 @@ func TestBasicAuthPassword(t *testing.T) {
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(payload))
|
||||
}))
|
||||
opts := options.NewOptions()
|
||||
opts := baseTestOptions()
|
||||
opts.Upstreams = append(opts.Upstreams, providerServer.URL)
|
||||
// The CookieSecret must be 32 bytes in order to create the AES
|
||||
// cipher.
|
||||
@ -564,9 +566,10 @@ func TestBasicAuthPassword(t *testing.T) {
|
||||
const emailAddress = "john.doe@example.com"
|
||||
|
||||
opts.SetProvider(NewTestProvider(providerURL, emailAddress))
|
||||
proxy := NewOAuthProxy(opts, func(email string) bool {
|
||||
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
||||
return email == emailAddress
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
|
||||
@ -611,11 +614,12 @@ func TestBasicAuthPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBasicAuthWithEmail(t *testing.T) {
|
||||
opts := options.NewOptions()
|
||||
opts := baseTestOptions()
|
||||
opts.PassBasicAuth = true
|
||||
opts.PassUserHeaders = false
|
||||
opts.PreferEmailToUser = false
|
||||
opts.BasicAuthPassword = "This is a secure password"
|
||||
opts.Cookie.Secret = rawCookieSecret
|
||||
validation.Validate(opts)
|
||||
|
||||
const emailAddress = "john.doe@example.com"
|
||||
@ -635,9 +639,10 @@ func TestBasicAuthWithEmail(t *testing.T) {
|
||||
{
|
||||
rw := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
|
||||
proxy := NewOAuthProxy(opts, func(email string) bool {
|
||||
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
||||
return email == emailAddress
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
proxy.addHeadersForProxying(rw, req, session)
|
||||
assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
|
||||
assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
|
||||
@ -648,9 +653,10 @@ func TestBasicAuthWithEmail(t *testing.T) {
|
||||
rw := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
|
||||
|
||||
proxy := NewOAuthProxy(opts, func(email string) bool {
|
||||
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
||||
return email == emailAddress
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
proxy.addHeadersForProxying(rw, req, session)
|
||||
assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
|
||||
assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
|
||||
@ -658,10 +664,11 @@ func TestBasicAuthWithEmail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPassUserHeadersWithEmail(t *testing.T) {
|
||||
opts := options.NewOptions()
|
||||
opts := baseTestOptions()
|
||||
opts.PassBasicAuth = false
|
||||
opts.PassUserHeaders = true
|
||||
opts.PreferEmailToUser = false
|
||||
opts.Cookie.Secret = base64CookieSecret
|
||||
validation.Validate(opts)
|
||||
|
||||
const emailAddress = "john.doe@example.com"
|
||||
@ -677,9 +684,10 @@ func TestPassUserHeadersWithEmail(t *testing.T) {
|
||||
{
|
||||
rw := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
|
||||
proxy := NewOAuthProxy(opts, func(email string) bool {
|
||||
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
||||
return email == emailAddress
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
proxy.addHeadersForProxying(rw, req, session)
|
||||
assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
|
||||
}
|
||||
@ -689,9 +697,10 @@ func TestPassUserHeadersWithEmail(t *testing.T) {
|
||||
rw := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
|
||||
|
||||
proxy := NewOAuthProxy(opts, func(email string) bool {
|
||||
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
||||
return email == emailAddress
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
proxy.addHeadersForProxying(rw, req, session)
|
||||
assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
|
||||
}
|
||||
@ -727,7 +736,7 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
|
||||
w.Write([]byte(payload))
|
||||
}))
|
||||
|
||||
t.opts = options.NewOptions()
|
||||
t.opts = baseTestOptions()
|
||||
t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL)
|
||||
if opts.ProxyUpstream != "" {
|
||||
t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream)
|
||||
@ -745,9 +754,13 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
|
||||
const emailAddress = "michael.bland@gsa.gov"
|
||||
|
||||
t.opts.SetProvider(NewTestProvider(providerURL, emailAddress))
|
||||
t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
|
||||
var err error
|
||||
t.proxy, err = NewOAuthProxy(t.opts, func(email string) bool {
|
||||
return email == emailAddress
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
@ -886,16 +899,20 @@ const signInSkipProvider = `>Found<`
|
||||
func NewSignInPageTest(skipProvider bool) *SignInPageTest {
|
||||
var sipTest SignInPageTest
|
||||
|
||||
sipTest.opts = options.NewOptions()
|
||||
sipTest.opts = baseTestOptions()
|
||||
sipTest.opts.Cookie.Secret = rawCookieSecret
|
||||
sipTest.opts.ClientID = "lkdgj"
|
||||
sipTest.opts.ClientSecret = "sgiufgoi"
|
||||
sipTest.opts.SkipProviderButton = skipProvider
|
||||
validation.Validate(sipTest.opts)
|
||||
|
||||
sipTest.proxy = NewOAuthProxy(sipTest.opts, func(email string) bool {
|
||||
var err error
|
||||
sipTest.proxy, err = NewOAuthProxy(sipTest.opts, func(email string) bool {
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
|
||||
sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
|
||||
|
||||
@ -987,7 +1004,7 @@ type OptionsModifier func(*options.Options)
|
||||
func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifier) *ProcessCookieTest {
|
||||
var pcTest ProcessCookieTest
|
||||
|
||||
pcTest.opts = options.NewOptions()
|
||||
pcTest.opts = baseTestOptions()
|
||||
for _, modifier := range modifiers {
|
||||
modifier(pcTest.opts)
|
||||
}
|
||||
@ -999,9 +1016,13 @@ func NewProcessCookieTest(opts ProcessCookieTestOpts, modifiers ...OptionsModifi
|
||||
pcTest.opts.Cookie.Refresh = time.Hour
|
||||
validation.Validate(pcTest.opts)
|
||||
|
||||
pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
|
||||
var err error
|
||||
pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
|
||||
return pcTest.validateUser
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pcTest.proxy.provider = &TestProvider{
|
||||
ValidToken: opts.providerValidateCookieResponse,
|
||||
}
|
||||
@ -1201,13 +1222,19 @@ func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
|
||||
func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
|
||||
var pcTest ProcessCookieTest
|
||||
|
||||
pcTest.opts = options.NewOptions()
|
||||
pcTest.opts = baseTestOptions()
|
||||
pcTest.opts.SetXAuthRequest = true
|
||||
validation.Validate(pcTest.opts)
|
||||
err := validation.Validate(pcTest.opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
|
||||
pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
|
||||
return pcTest.validateUser
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pcTest.proxy.provider = &TestProvider{
|
||||
ValidToken: true,
|
||||
}
|
||||
@ -1232,14 +1259,18 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
|
||||
func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
|
||||
var pcTest ProcessCookieTest
|
||||
|
||||
pcTest.opts = options.NewOptions()
|
||||
pcTest.opts = baseTestOptions()
|
||||
pcTest.opts.SetXAuthRequest = true
|
||||
pcTest.opts.SetBasicAuth = true
|
||||
validation.Validate(pcTest.opts)
|
||||
|
||||
pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
|
||||
var err error
|
||||
pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
|
||||
return pcTest.validateUser
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pcTest.proxy.provider = &TestProvider{
|
||||
ValidToken: true,
|
||||
}
|
||||
@ -1266,14 +1297,18 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
|
||||
func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
|
||||
var pcTest ProcessCookieTest
|
||||
|
||||
pcTest.opts = options.NewOptions()
|
||||
pcTest.opts = baseTestOptions()
|
||||
pcTest.opts.SetXAuthRequest = true
|
||||
pcTest.opts.SetBasicAuth = false
|
||||
validation.Validate(pcTest.opts)
|
||||
|
||||
pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
|
||||
var err error
|
||||
pcTest.proxy, err = NewOAuthProxy(pcTest.opts, func(email string) bool {
|
||||
return pcTest.validateUser
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pcTest.proxy.provider = &TestProvider{
|
||||
ValidToken: true,
|
||||
}
|
||||
@ -1303,18 +1338,16 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) {
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
opts := options.NewOptions()
|
||||
opts := baseTestOptions()
|
||||
opts.Upstreams = append(opts.Upstreams, upstream.URL)
|
||||
opts.ClientID = "aljsal"
|
||||
opts.ClientSecret = "jglkfsdgj"
|
||||
opts.Cookie.Secret = base64CookieSecret
|
||||
opts.SkipAuthPreflight = true
|
||||
validation.Validate(opts)
|
||||
|
||||
upstreamURL, _ := url.Parse(upstream.URL)
|
||||
opts.SetProvider(NewTestProvider(upstreamURL, ""))
|
||||
|
||||
proxy := NewOAuthProxy(opts, func(string) bool { return false })
|
||||
proxy, err := NewOAuthProxy(opts, func(string) bool { return false })
|
||||
assert.NoError(t, err)
|
||||
rw := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("OPTIONS", "/preflight-request", nil)
|
||||
proxy.ServeHTTP(rw, req)
|
||||
@ -1353,7 +1386,7 @@ type SignatureTest struct {
|
||||
}
|
||||
|
||||
func NewSignatureTest() *SignatureTest {
|
||||
opts := options.NewOptions()
|
||||
opts := baseTestOptions()
|
||||
opts.Cookie.Secret = rawCookieSecret
|
||||
opts.ClientID = "client ID"
|
||||
opts.ClientSecret = "client secret"
|
||||
@ -1409,7 +1442,10 @@ func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
proxy := NewOAuthProxy(st.opts, func(email string) bool { return true })
|
||||
proxy, err := NewOAuthProxy(st.opts, func(email string) bool { return true })
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var bodyBuf io.ReadCloser
|
||||
if body != "" {
|
||||
@ -1461,10 +1497,12 @@ func TestRequestSignaturePostRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetRedirect(t *testing.T) {
|
||||
options := options.NewOptions()
|
||||
_ = validation.Validate(options)
|
||||
require.NotEmpty(t, options.ProxyPrefix)
|
||||
proxy := NewOAuthProxy(options, func(s string) bool { return false })
|
||||
opts := baseTestOptions()
|
||||
err := validation.Validate(opts)
|
||||
assert.NoError(t, err)
|
||||
require.NotEmpty(t, opts.ProxyPrefix)
|
||||
proxy, err := NewOAuthProxy(opts, func(s string) bool { return false })
|
||||
assert.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -1500,14 +1538,19 @@ type ajaxRequestTest struct {
|
||||
|
||||
func newAjaxRequestTest() *ajaxRequestTest {
|
||||
test := &ajaxRequestTest{}
|
||||
test.opts = options.NewOptions()
|
||||
test.opts = baseTestOptions()
|
||||
test.opts.Cookie.Secret = base64CookieSecret
|
||||
test.opts.ClientID = "gkljfdl"
|
||||
test.opts.ClientSecret = "sdflkjs"
|
||||
validation.Validate(test.opts)
|
||||
test.proxy = NewOAuthProxy(test.opts, func(email string) bool {
|
||||
|
||||
var err error
|
||||
test.proxy, err = NewOAuthProxy(test.opts, func(email string) bool {
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return test
|
||||
}
|
||||
|
||||
@ -1558,11 +1601,14 @@ func TestAjaxForbiddendRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClearSplitCookie(t *testing.T) {
|
||||
opts := options.NewOptions()
|
||||
opts := baseTestOptions()
|
||||
opts.Cookie.Secret = base64CookieSecret
|
||||
opts.Cookie.Name = "oauth2"
|
||||
opts.Cookie.Domains = []string{"abc"}
|
||||
store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
|
||||
assert.Equal(t, err, nil)
|
||||
cipher, err := encryption.NewBase64Cipher(encryption.NewCFBCipher, encryption.SecretBytes(opts.Cookie.Secret))
|
||||
assert.Equal(t, nil, err)
|
||||
store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie, cipher)
|
||||
assert.Equal(t, nil, err)
|
||||
p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
|
||||
var rw = httptest.NewRecorder()
|
||||
req := httptest.NewRequest("get", "/", nil)
|
||||
@ -1587,11 +1633,13 @@ func TestClearSplitCookie(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestClearSingleCookie(t *testing.T) {
|
||||
opts := options.NewOptions()
|
||||
opts := baseTestOptions()
|
||||
opts.Cookie.Name = "oauth2"
|
||||
opts.Cookie.Domains = []string{"abc"}
|
||||
store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie)
|
||||
assert.Equal(t, err, nil)
|
||||
cipher, err := encryption.NewBase64Cipher(encryption.NewCFBCipher, encryption.SecretBytes(opts.Cookie.Secret))
|
||||
assert.Equal(t, nil, err)
|
||||
store, err := cookie.NewCookieSessionStore(&opts.Session, &opts.Cookie, cipher)
|
||||
assert.Equal(t, nil, err)
|
||||
p := OAuthProxy{CookieName: opts.Cookie.Name, CookieDomains: opts.Cookie.Domains, sessionStore: store}
|
||||
var rw = httptest.NewRecorder()
|
||||
req := httptest.NewRequest("get", "/", nil)
|
||||
@ -1768,13 +1816,14 @@ func Test_noCacheHeadersDoesNotExistsInResponseHeadersFromUpstream(t *testing.T)
|
||||
}))
|
||||
t.Cleanup(upstream.Close)
|
||||
|
||||
opts := options.NewOptions()
|
||||
opts := baseTestOptions()
|
||||
opts.Upstreams = []string{upstream.URL}
|
||||
opts.SkipAuthRegex = []string{".*"}
|
||||
_ = validation.Validate(opts)
|
||||
proxy := NewOAuthProxy(opts, func(email string) bool {
|
||||
proxy, err := NewOAuthProxy(opts, func(email string) bool {
|
||||
return true
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/upstream", nil)
|
||||
@ -1788,3 +1837,12 @@ func Test_noCacheHeadersDoesNotExistsInResponseHeadersFromUpstream(t *testing.T)
|
||||
assert.Equal(t, "", rec.Header().Get(k))
|
||||
}
|
||||
}
|
||||
|
||||
func baseTestOptions() *options.Options {
|
||||
opts := options.NewOptions()
|
||||
opts.Cookie.Secret = rawCookieSecret
|
||||
opts.ClientID = "cliend-id"
|
||||
opts.ClientSecret = "client-secret"
|
||||
opts.EmailDomains = []string{"*"}
|
||||
return opts
|
||||
}
|
||||
|
Reference in New Issue
Block a user