Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introducing option to authenticate with (personal access) token… #27

Merged
merged 7 commits into from
Nov 18, 2024
16 changes: 12 additions & 4 deletions lib/DefaultOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,25 @@ export class DefaultOptions extends Options {
name: 'user',
flag: 'u',
description: 'Username for checking all confluence documents',
required: true,
required: false,
})
confluenceUser: string
confluenceUser = ''

@option({
name: 'password',
flag: 'p',
description: 'Password for the user',
required: true,
required: false,
})
confluencePassword = ''

@option({
name: 'token',
flag: 't',
description: 'Personal Access Token for the user. If set user and password will be ignored',
required: false,
})
confluencePassword: string
confluencePersonalAccessToken = ''

@option({
description: 'Log-Level to use (trace, debug, verbose, info, warn, error)',
Expand Down
33 changes: 27 additions & 6 deletions lib/api/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export class Configuration {
*/
public confluencePassword: string

/**
* The personal access token of the Confluence user
*/
public confluencePersonalAccessToken: string

/**
* The document id of the configuration document
*/
Expand Down Expand Up @@ -94,10 +99,17 @@ export class Configuration {
*/
private _log: Logger

constructor(confluenceUrl: string, confluenceUser: string, confluencePassword: string, configurationDocumentId: string) {
constructor(
confluenceUrl: string,
confluenceUser: string,
confluencePassword: string,
confluencePersonalAccessToken: string,
configurationDocumentId: string
) {
this.confluenceUrl = confluenceUrl
this.confluenceUser = confluenceUser
this.confluencePassword = confluencePassword
this.confluencePersonalAccessToken = confluencePersonalAccessToken
this.configurationDocumentId = configurationDocumentId
this._loaded = false

Expand Down Expand Up @@ -134,11 +146,20 @@ export class Configuration {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let configurationDocument: any
try {
configurationDocument = await got(configurationUrl, {
username: this.confluenceUser,
password: this.confluencePassword,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}).json<any>()
if (this.confluencePersonalAccessToken !== '') {
configurationDocument = await got(configurationUrl, {
dploeger marked this conversation as resolved.
Show resolved Hide resolved
headers: {
Authorization: 'Bearer ' + this.confluencePersonalAccessToken,
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}).json<any>()
} else {
configurationDocument = await got(configurationUrl, {
username: this.confluenceUser,
password: this.confluencePassword,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}).json<any>()
}
} catch (e) {
this._log.error(`Can't fetch configuration document: (${e.name}) ${e.message}`)
throw e
Expand Down
116 changes: 83 additions & 33 deletions lib/api/Confluence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ export class Confluence {
public confluenceUrl: string
public confluenceUser: string
public confluencePassword: string
public confluencePersonalAccessToken: string
private _log: Logger

constructor(confluenceUrl: string, confluenceUser: string, confluencePassword: string) {
constructor(confluenceUrl: string, confluenceUser: string, confluencePassword: string, confluencePersonalAccessToken: string) {
this.confluenceUrl = confluenceUrl
this.confluenceUser = confluenceUser
this.confluencePassword = confluencePassword
this.confluencePersonalAccessToken = confluencePersonalAccessToken

this._log = log.getLogger('Confluence')
}
Expand All @@ -47,11 +49,20 @@ export class Confluence {
this._log.debug(`Searching for documents with ${cql}`)
do {
const configurationUrl = `${this.confluenceUrl}/rest/api/content/search?cql=${cql}&start=${start}&limit=${limit}`
results = await got(configurationUrl, {
username: this.confluenceUser,
password: this.confluencePassword,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}).json<any>()
if (this.confluencePersonalAccessToken !== '') {
results = await got(configurationUrl, {
headers: {
Authorization: 'Bearer ' + this.confluencePersonalAccessToken,
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}).json<any>()
} else {
results = await got(configurationUrl, {
username: this.confluenceUser,
password: this.confluencePassword,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}).json<any>()
}
for (const result of results.results) {
documentInfos.push(await this.getDocumentInfo(result.id))
}
Expand All @@ -69,12 +80,21 @@ export class Confluence {
public async getDocumentInfo(documentId: number): Promise<DocumentInfo> {
this._log.debug(`Getting document information of document ${documentId}`)
const documentUrl = `${this.confluenceUrl}/rest/api/content/${documentId}?expand=ancestors,version,metadata.labels,history`
const document = await got(documentUrl, {
username: this.confluenceUser,
password: this.confluencePassword,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}).json<any>()

let document
if (this.confluencePersonalAccessToken !== '') {
document = await got(documentUrl, {
headers: {
Authorization: 'Bearer ' + this.confluencePersonalAccessToken,
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}).json<any>()
} else {
document = await got(documentUrl, {
username: this.confluenceUser,
password: this.confluencePassword,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}).json<any>()
}
const author = document.version.by.username ?? null
const creator = document.history['createdBy'].username ?? null

Expand Down Expand Up @@ -142,31 +162,61 @@ export class Confluence {
public async createConfigurationDocument(space: string, title: string, parentId: string): Promise<string> {
const template = await fs.promises.readFile(path.join(__dirname, '..', '..', 'resources', 'configurationDocument.html'), 'utf-8')

const response = await got
.post(`${this.confluenceUrl}/rest/api/content`, {
json: {
type: 'page',
title: title,
space: {
key: space,
let response: any
if (this.confluencePersonalAccessToken !== '') {
response = await got
.post(`${this.confluenceUrl}/rest/api/content`, {
json: {
type: 'page',
title: title,
space: {
key: space,
},
ancestors: [
{
id: parentId,
},
],
body: {
storage: {
value: template,
representation: 'storage',
},
},
},
headers: {
Authorization: 'Bearer ' + this.confluencePersonalAccessToken,
},
ancestors: [
{
id: parentId,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.json<any>()
} else {
response = await got
.post(`${this.confluenceUrl}/rest/api/content`, {
json: {
type: 'page',
title: title,
space: {
key: space,
},
],
body: {
storage: {
value: template,
representation: 'storage',
ancestors: [
{
id: parentId,
},
],
body: {
storage: {
value: template,
representation: 'storage',
},
},
},
},
username: this.confluenceUser,
password: this.confluencePassword,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.json<any>()
username: this.confluenceUser,
password: this.confluencePassword,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.json<any>()
}
return response.id
}
}
13 changes: 12 additions & 1 deletion lib/commands/Check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,28 @@ export default class extends Command {
public async execute(options: CheckOptions): Promise<void> {
const log = options.getLogger()

if (options.confluencePersonalAccessToken === '' && (options.confluenceUser === '' || options.confluencePassword === '')) {
log.error('user and/or password parameter not set or empty! When not using the token parameter both of these have to be set!')
return
}

log.info('Checking for outdated documents')

const configuration = new Configuration(
options.confluenceUrl,
options.confluenceUser,
options.confluencePassword,
options.confluencePersonalAccessToken,
options.configurationDocumentId
)
await configuration.load()

const confluence = new Confluence(options.confluenceUrl, options.confluenceUser, options.confluencePassword)
const confluence = new Confluence(
options.confluenceUrl,
options.confluenceUser,
options.confluencePassword,
options.confluencePersonalAccessToken
)

const notification = new Notification(configuration, options.smtpTransportUrl, confluence, null, options.dryRun)

Expand Down
12 changes: 11 additions & 1 deletion lib/commands/CreateConfigurationDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,19 @@ export default class extends Command {
public async execute(options: CheckOptions): Promise<string> {
const log = options.getLogger()

if (options.confluencePersonalAccessToken === '' && (options.confluenceUser === '' || options.confluencePassword === '')) {
log.error('user and/or password parameter not set or empty! When not using the token parameter both of these have to be set!')
return
}

log.info('Checking for outdated documents')

const confluence = new Confluence(options.confluenceUrl, options.confluenceUser, options.confluencePassword)
const confluence = new Confluence(
options.confluenceUrl,
options.confluenceUser,
options.confluencePassword,
options.confluencePersonalAccessToken
)

const pageId = await confluence.createConfigurationDocument(options.space, options.title, options.parentId)

Expand Down
6 changes: 3 additions & 3 deletions test/ConfigurationTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('The Configuration API', (): void => {
const mockServer = new MockServer('https://example.com')
mockServer.addConfigurationDocumentEndpoint()

const configuration = new Configuration('https://example.com', 'nobody', 'nothing', '12345')
const configuration = new Configuration('https://example.com', 'nobody', 'nothing', '', '12345')
await configuration.load()
chai.expect(configuration.checks).to.have.lengthOf(2)
chai.expect(configuration.checks[0].labels).to.contain('test1')
Expand All @@ -31,7 +31,7 @@ describe('The Configuration API', (): void => {
const mockServer = new MockServer('https://example.com')
mockServer.addConfigurationDocumentEndpoint()

const configuration = new Configuration('https://example.com', 'nobody', 'nothing', '12346')
const configuration = new Configuration('https://example.com', 'nobody', 'nothing', '', '12346')
await configuration.load()

chai.expect(configuration.exceptions).to.have.lengthOf(0)
Expand All @@ -40,7 +40,7 @@ describe('The Configuration API', (): void => {
const mockServer = new MockServer('https://example.com')
mockServer.addConfigurationDocumentEndpoint()

const configuration = new Configuration('https://example.com', 'nobody', 'nothing', '12347')
const configuration = new Configuration('https://example.com', 'nobody', 'nothing', '', '12347')
await configuration.load()
chai.expect(configuration.maintainer).to.have.lengthOf(1)
})
Expand Down
37 changes: 35 additions & 2 deletions test/ConfluenceTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('The Confluence API', (): void => {
const mockServer = new MockServer('https://example.com')
mockServer.addSearchEndpoint()
mockServer.addDocumentEndpoint()
const confluence = new Confluence('https://example.com', 'nobody', 'nothing')
const confluence = new Confluence('https://example.com', 'nobody', 'nothing', '')
const results = await confluence.findDocumentsOlderThan('', 1, 1)
chai.expect(results).to.have.lengthOf(2)
chai.expect(results[0].url).to.eq('https://example.com/display/SAMPLE/Test')
Expand All @@ -35,7 +35,40 @@ describe('The Confluence API', (): void => {
it('should add a configuration document', async (): Promise<void> => {
const mockServer = new MockServer('https://example.com')
mockServer.addCreateEndpoint()
const confluence = new Confluence('https://example.com', 'nobody', 'nothing')
const confluence = new Confluence('https://example.com', 'nobody', 'nothing', '')
const result = await confluence.createConfigurationDocument('example', 'test', '0123')
chai.expect(result).to.eq('12345')
})

it('should search for old documents using an access token', async (): Promise<void> => {
const mockServer = new MockServer('https://example.com')
mockServer.addSearchEndpointToken()
mockServer.addDocumentEndpointToken()
const confluence = new Confluence('https://example.com', 'nobody', '', 'nothing')
const results = await confluence.findDocumentsOlderThan('', 1, 1)
chai.expect(results).to.have.lengthOf(2)
chai.expect(results[0].url).to.eq('https://example.com/display/SAMPLE/Test')
chai.expect(results[0].shortUrl).to.eq('/display/SAMPLE/Test')
chai.expect(results[0].author).to.eq('author')
chai.expect(results[0].id).to.eq(123)
chai.expect(results[0].lastVersionDate).to.eq('2019-12-31T22:00:00.000Z')
chai.expect(results[0].lastVersionMessage).to.eq('Some change')
chai.expect(results[0].title).to.eq('Test')
chai.expect(results[1].url).to.eq('https://example.com/display/SAMPLE/Test2')
chai.expect(results[1].shortUrl).to.eq('/display/SAMPLE/Test2')
chai.expect(results[1].author).to.eq('author2')
chai.expect(results[1].id).to.eq(234)
chai.expect(results[1].lastVersionDate).to.eq('2020-01-31T22:00:00.000Z')
chai.expect(results[1].lastVersionMessage).to.eq('')
chai.expect(results[1].title).to.eq('Test2')
chai.expect(results[0].labels.length).to.eq(1)
chai.expect(results[0].labels[0]).to.eq('Test')
})

it('should add a configuration document using an access token', async (): Promise<void> => {
const mockServer = new MockServer('https://example.com')
mockServer.addCreateEndpointToken()
const confluence = new Confluence('https://example.com', 'nobody', '', 'nothing')
const result = await confluence.createConfigurationDocument('example', 'test', '0123')
chai.expect(result).to.eq('12345')
})
Expand Down
Loading
Loading