Skip to content

Commit

Permalink
Merge pull request #27 from dodevops/feature/te/DO-1363/add-auth-with…
Browse files Browse the repository at this point in the history
…-pat

feat: Introducing option to authenticate with (personal access) token…
  • Loading branch information
dploeger authored Nov 18, 2024
2 parents 1ca8fd3 + 82c975c commit deb5c38
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 54 deletions.
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, {
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

0 comments on commit deb5c38

Please sign in to comment.