1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-03 01:39:37 +02:00

Add official openid plugin tests

This commit is contained in:
Chocobozzz 2025-08-07 14:41:41 +02:00
parent fc986076c9
commit 83f74169da
No known key found for this signature in database
GPG key ID: 583A612D890159BE
5 changed files with 190 additions and 5 deletions

View file

@ -38,6 +38,14 @@ jobs:
ports: ports:
- 9444:9000 - 9444:9000
keycloak:
image: chocobozzz/peertube-tests-keycloak
ports:
- 8082:8080
env:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:

View file

@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable @typescript-eslint/no-floating-promises */
import { decode } from 'querystring'
import request from 'supertest'
import { URL } from 'url'
import { pick, queryParamsToObject } from '@peertube/peertube-core-utils' import { pick, queryParamsToObject } from '@peertube/peertube-core-utils'
import { HttpStatusCode, HttpStatusCodeType } from '@peertube/peertube-models' import { HttpStatusCode, HttpStatusCodeType } from '@peertube/peertube-models'
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils' import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
import { decode } from 'querystring'
import request from 'supertest'
import { URL } from 'url'
export type CommonRequestParams = { export type CommonRequestParams = {
url: string url: string
@ -32,16 +32,19 @@ export function makeRawRequest (options: {
responseType?: string responseType?: string
range?: string range?: string
query?: { [id: string]: string } query?: { [id: string]: string }
fields?: { [fieldName: string]: any }
method?: 'GET' | 'POST' method?: 'GET' | 'POST'
accept?: string accept?: string
headers?: { [name: string]: string } headers?: { [name: string]: string }
redirects?: number redirects?: number
requestType?: 'form'
}) { }) {
const { host, protocol, pathname, searchParams } = new URL(options.url) const { host, protocol, pathname, searchParams } = new URL(options.url)
const reqOptions = { const reqOptions = {
url: `${protocol}//${host}`, url: `${protocol}//${host}`,
path: pathname, path: pathname,
type: options.requestType,
contentType: undefined, contentType: undefined,
@ -51,7 +54,7 @@ export function makeRawRequest (options: {
...queryParamsToObject(searchParams) ...queryParamsToObject(searchParams)
}, },
...pick(options, [ 'expectedStatus', 'range', 'token', 'headers', 'responseType', 'accept', 'redirects' ]) ...pick(options, [ 'expectedStatus', 'range', 'token', 'headers', 'responseType', 'accept', 'redirects', 'fields' ])
} }
if (options.method === 'POST') { if (options.method === 'POST') {
@ -161,12 +164,19 @@ export function makeUploadRequest (
export function makePostBodyRequest ( export function makePostBodyRequest (
options: CommonRequestParams & { options: CommonRequestParams & {
fields?: { [fieldName: string]: any } fields?: { [fieldName: string]: any }
requestType?: 'form'
} }
) { ) {
const req = request(options.url).post(options.path) const req = request(options.url).post(options.path)
.send(options.fields) .send(options.fields)
return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options }) return buildRequest(req, {
accept: 'application/json',
type: options.requestType,
expectedStatus: HttpStatusCode.BAD_REQUEST_400,
...options
})
} }
export function makePutBodyRequest (options: { export function makePutBodyRequest (options: {

View file

@ -200,6 +200,9 @@ describe('Test resumable upload', function () {
}) })
it('Should not accept more chunks than expected with an invalid content length', async function () { it('Should not accept more chunks than expected with an invalid content length', async function () {
// Sometimes the server answers 409, and sometimes 400 :shrug:
this.retries(3)
const uploadId = await prepareUpload({ size: 500 }) const uploadId = await prepareUpload({ size: 500 })
const size = 1000 const size = 1000

View file

@ -0,0 +1,163 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode } from '@peertube/peertube-models'
import {
cleanupTests,
createSingleServer,
makeRawRequest,
PeerTubeServer,
setAccessTokensToServers
} from '@peertube/peertube-server-commands'
import { expect } from 'chai'
import { Response } from 'supertest'
const oauthServerHost = '127.0.0.1'
const oauthServerPort = 8082
describe('Official plugin auth-openid-connect', function () {
let server: PeerTubeServer
let openIdLoginUrl: string
const pluginSettings = {
'auth-display-name': 'OpenID Connect',
'discover-url': `http://${oauthServerHost}:${oauthServerPort}/realms/myrealm`,
'client-id': 'myclient',
'client-secret': 'D9MdqzGSnlfWJq00e9mBzI31OPn9WXyg',
'scope': 'openid email profile',
'username-property': 'email',
'mail-property': 'email',
'logout-redirect-uri': '',
'display-name-property': 'email',
'role-property': '',
'group-property': '',
'allowed-group': '',
'signature-algorithm': 'RS256'
}
before(async function () {
this.timeout(30000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await server.plugins.install({ npmName: 'peertube-plugin-auth-openid-connect' })
await server.plugins.updateSettings({
npmName: 'peertube-plugin-auth-openid-connect',
settings: pluginSettings
})
})
it('Should load openid connect plugin', async function () {
const config = await server.config.getConfig()
const { name, version, authName } = config.plugin.registeredExternalAuths[0]
openIdLoginUrl = server.url + `/plugins/${name}/${version}/auth/${authName}`
})
it('Should login with the appropriate username/password', async function () {
const peertubeRes = await getOpenIdUrl(openIdLoginUrl)
const kcRes = await loginOnKeycloak(extractLocation(peertubeRes))
const ptBypassPath = await sendBackKeycloakCode(peertubeRes, kcRes)
const externalAuthToken = new URL(ptBypassPath, server.url).searchParams.get('externalAuthToken')
const { body } = await server.login.loginUsingExternalToken({ username: 'myuser_example.com', externalAuthToken })
const { username } = await server.users.getMyInfo({ token: body.access_token })
expect(username).to.equal('myuser_example.com')
})
after(async function () {
await cleanupTests([ server ])
})
})
function extractActionUrl (text: string) {
const matched = text.match(/<form[^>]+action="([^"]+)"/i)
if (!matched) {
console.error(text)
throw new Error('Cannot find action URL in the login page')
}
return matched[1].replace(/&amp;/g, '&')
}
function extractInputValue (text: string, name: string) {
const match = text.match(new RegExp(`<input[^>]+name="${name}"[^>]+value="([^"]+)"`, 'i'))
return match[1]
}
async function getOpenIdUrl (openIdLoginUrl: string) {
const peertubeRes = await makeRawRequest({ url: openIdLoginUrl, expectedStatus: HttpStatusCode.FOUND_302 })
const kcLocation = peertubeRes.headers['location']
expect(kcLocation).to.include(`http://${oauthServerHost}:${oauthServerPort}/realms/myrealm/protocol/openid-connect/auth?`)
const parsed = new URL(kcLocation)
expect(parsed.searchParams.get('client_id')).to.equal('myclient')
expect(parsed.searchParams.get('scope')).to.equal('openid email profile')
expect(parsed.searchParams.get('response_type')).to.equal('code')
return peertubeRes
}
async function loginOnKeycloak (loginPageUrl: string) {
const resLoginPage = await makeRawRequest({ url: loginPageUrl, expectedStatus: HttpStatusCode.OK_200 })
expect(resLoginPage.text).to.include('Sign in to your account')
const cookies = extractCookies(resLoginPage)
const actionUrl = extractActionUrl(resLoginPage.text)
const res = await makeRawRequest({
url: actionUrl,
method: 'POST',
requestType: 'form',
fields: {
username: 'myuser',
password: 'coucou'
},
headers: {
Cookie: cookies
},
expectedStatus: HttpStatusCode.OK_200
})
return res
}
async function sendBackKeycloakCode (peertubeRes: Response, kcRes: Response) {
const kcText = kcRes.text
const res = await makeRawRequest({
url: extractActionUrl(kcText),
method: 'POST',
headers: {
Cookie: extractCookies(peertubeRes)
},
fields: {
code: extractInputValue(kcText, 'code'),
iss: extractInputValue(kcText, 'iss'),
state: extractInputValue(kcText, 'state'),
session_state: extractInputValue(kcText, 'session_state')
},
expectedStatus: HttpStatusCode.FOUND_302
})
const ptBypassPath = res.headers['location']
expect(ptBypassPath).to.include('/login?externalAuthToken=')
expect(ptBypassPath).to.include('username=myuser_example.com')
return ptBypassPath
}
function extractCookies (res: Response) {
return res.get('Set-Cookie').join('; ')
}
function extractLocation (res: Response) {
const location = res.headers['location']
if (!location) throw new Error('No location header found in response')
return location
}

View file

@ -1,5 +1,6 @@
import './akismet' import './akismet'
import './auth-ldap' import './auth-ldap'
import './auth-openid-connect'
import './auto-block-videos' import './auto-block-videos'
import './auto-mute' import './auto-mute'
import './privacy-remover' import './privacy-remover'