Browse Source

Add user adminFlags

Chocobozzz 5 years ago
parent
commit
1eddc9a74f
65 changed files with 953 additions and 562 deletions
  1. 6 1
      client/src/app/+admin/users/user-edit/user-create.component.ts
  2. 7 0
      client/src/app/+admin/users/user-edit/user-edit.component.html
  3. 5 0
      client/src/app/+admin/users/user-edit/user-edit.ts
  4. 6 2
      client/src/app/+admin/users/user-edit/user-update.component.ts
  5. 1 1
      client/src/app/shared/users/user-notifications.component.html
  6. 10 1
      client/src/app/shared/users/user.model.ts
  7. 2 0
      server.ts
  8. 6 3
      server/controllers/api/users/index.ts
  9. 6 6
      server/controllers/api/users/me.ts
  10. 66 2
      server/helpers/custom-jsonld-signature.ts
  11. 7 1
      server/helpers/custom-validators/users.ts
  12. 1 1
      server/initializers/constants.ts
  13. 40 0
      server/initializers/migrations/0365-user-admin-flags.ts
  14. 4 2
      server/lib/video-blacklist.ts
  15. 3 0
      server/middlewares/validators/users.ts
  16. 17 1
      server/models/account/user.ts
  17. 1 1
      server/tests/api/activitypub/fetch.ts
  18. 1 1
      server/tests/api/check-params/blocklist.ts
  19. 1 1
      server/tests/api/check-params/config.ts
  20. 1 1
      server/tests/api/check-params/debug.ts
  21. 1 1
      server/tests/api/check-params/follows.ts
  22. 1 1
      server/tests/api/check-params/jobs.ts
  23. 1 1
      server/tests/api/check-params/logs.ts
  24. 1 1
      server/tests/api/check-params/redundancy.ts
  25. 1 1
      server/tests/api/check-params/user-subscriptions.ts
  26. 22 2
      server/tests/api/check-params/users.ts
  27. 1 1
      server/tests/api/check-params/video-abuses.ts
  28. 6 7
      server/tests/api/check-params/video-blacklist.ts
  29. 1 1
      server/tests/api/check-params/video-captions.ts
  30. 1 1
      server/tests/api/check-params/video-channels.ts
  31. 1 1
      server/tests/api/check-params/video-comments.ts
  32. 2 2
      server/tests/api/check-params/video-imports.ts
  33. 10 8
      server/tests/api/check-params/videos-filter.ts
  34. 2 2
      server/tests/api/check-params/videos.ts
  35. 7 1
      server/tests/api/notifications/user-notifications.ts
  36. 2 2
      server/tests/api/search/search-activitypub-video-channels.ts
  37. 1 1
      server/tests/api/server/email.ts
  38. 1 1
      server/tests/api/server/follow-constraints.ts
  39. 1 1
      server/tests/api/server/follows.ts
  40. 1 1
      server/tests/api/server/stats.ts
  41. 3 3
      server/tests/api/users/blocklist.ts
  42. 1 1
      server/tests/api/users/user-subscriptions.ts
  43. 6 1
      server/tests/api/users/users-multiple-servers.ts
  44. 476 416
      server/tests/api/users/users.ts
  45. 1 1
      server/tests/api/videos/multiple-servers.ts
  46. 101 14
      server/tests/api/videos/video-blacklist.ts
  47. 28 4
      server/tests/api/videos/video-change-ownership.ts
  48. 1 1
      server/tests/api/videos/video-channels.ts
  49. 1 1
      server/tests/api/videos/video-nsfw.ts
  50. 6 1
      server/tests/api/videos/video-playlists.ts
  51. 1 1
      server/tests/api/videos/video-privacy.ts
  52. 9 7
      server/tests/api/videos/videos-filter.ts
  53. 1 1
      server/tests/api/videos/videos-history.ts
  54. 1 1
      server/tests/cli/peertube.ts
  55. 1 1
      server/tests/cli/reset-password.ts
  56. 1 1
      server/tests/cli/update-host.ts
  57. 1 1
      server/tests/feeds/feeds.ts
  58. 2 2
      server/tests/misc-endpoints.ts
  59. 1 1
      server/tests/real-world/populate-database.ts
  60. 2 0
      shared/models/users/user-create.model.ts
  61. 4 0
      shared/models/users/user-flag.model.ts
  62. 2 0
      shared/models/users/user-update.model.ts
  63. 5 0
      shared/models/users/user.model.ts
  64. 25 8
      shared/utils/users/users.ts
  65. 18 33
      shared/utils/videos/video-blacklist.ts

+ 6 - 1
client/src/app/+admin/users/user-edit/user-create.component.ts

@@ -8,6 +8,7 @@ import { FormValidatorService } from '@app/shared/forms/form-validators/form-val
 import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
 import { ConfigService } from '@app/+admin/config/shared/config.service'
 import { UserService } from '@app/shared'
+import { UserAdminFlag } from '@shared/models/users/user-flag.model'
 
 @Component({
   selector: 'my-user-create',
@@ -45,7 +46,8 @@ export class UserCreateComponent extends UserEdit implements OnInit {
       password: this.userValidatorsService.USER_PASSWORD,
       role: this.userValidatorsService.USER_ROLE,
       videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
-      videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY
+      videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY,
+      byPassAutoBlacklist: null
     }, defaultValues)
   }
 
@@ -54,8 +56,11 @@ export class UserCreateComponent extends UserEdit implements OnInit {
 
     const userCreate: UserCreate = this.form.value
 
+    userCreate.adminFlags = this.buildAdminFlags(this.form.value)
+
     // A select in HTML is always mapped as a string, we convert it to number
     userCreate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
+    userCreate.videoQuotaDaily = parseInt(this.form.value['videoQuotaDaily'], 10)
 
     this.userService.addUser(userCreate).subscribe(
       () => {

+ 7 - 0
client/src/app/+admin/users/user-edit/user-edit.component.html

@@ -79,6 +79,13 @@
     </div>
   </div>
 
+  <div class="form-group">
+    <my-peertube-checkbox
+      inputName="byPassAutoBlacklist" formControlName="byPassAutoBlacklist"
+      i18n-labelText labelText="Bypass video auto blacklist"
+    ></my-peertube-checkbox>
+  </div>
+
   <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
 </form>
 

+ 5 - 0
client/src/app/+admin/users/user-edit/user-edit.ts

@@ -2,6 +2,7 @@ import { ServerService } from '../../../core'
 import { FormReactive } from '../../../shared'
 import { USER_ROLE_LABELS, VideoResolution } from '../../../../../../shared'
 import { ConfigService } from '@app/+admin/config/shared/config.service'
+import { UserAdminFlag } from '@shared/models/users/user-flag.model'
 
 export abstract class UserEdit extends FormReactive {
   videoQuotaOptions: { value: string, label: string }[] = []
@@ -42,6 +43,10 @@ export abstract class UserEdit extends FormReactive {
     return
   }
 
+  protected buildAdminFlags (formValue: any) {
+    return formValue.byPassAutoBlacklist ? UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST : UserAdminFlag.NONE
+  }
+
   protected buildQuotaOptions () {
     // These are used by a HTML select, so convert key into strings
     this.videoQuotaOptions = this.configService

+ 6 - 2
client/src/app/+admin/users/user-edit/user-update.component.ts

@@ -10,6 +10,7 @@ import { FormValidatorService } from '@app/shared/forms/form-validators/form-val
 import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
 import { ConfigService } from '@app/+admin/config/shared/config.service'
 import { UserService } from '@app/shared'
+import { UserAdminFlag } from '@shared/models/users/user-flag.model'
 
 @Component({
   selector: 'my-user-update',
@@ -46,7 +47,8 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
       email: this.userValidatorsService.USER_EMAIL,
       role: this.userValidatorsService.USER_ROLE,
       videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
-      videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY
+      videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY,
+      byPassAutoBlacklist: null
     }, defaultValues)
 
     this.paramsSub = this.route.params.subscribe(routeParams => {
@@ -67,6 +69,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
     this.error = undefined
 
     const userUpdate: UserUpdate = this.form.value
+    userUpdate.adminFlags = this.buildAdminFlags(this.form.value)
 
     // A select in HTML is always mapped as a string, we convert it to number
     userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
@@ -111,7 +114,8 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
       email: userJson.email,
       role: userJson.role,
       videoQuota: userJson.videoQuota,
-      videoQuotaDaily: userJson.videoQuotaDaily
+      videoQuotaDaily: userJson.videoQuotaDaily,
+      byPassAutoBlacklist: userJson.adminFlags & UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST
     })
   }
 }

+ 1 - 1
client/src/app/shared/users/user-notifications.component.html

@@ -107,7 +107,7 @@
         <my-global-icon iconName="users"></my-global-icon>
 
         <div class="message">
-          Your instance has <a (click)="markAsRead(notification)" [routerLink]="notification.instanceFollowUrl">a new follower</a>
+          Your instance has <a (click)="markAsRead(notification)" [routerLink]="notification.instanceFollowUrl">a new follower</a> ({{ notification.actorFollow.follower.host }})
           <ng-container *ngIf="notification.actorFollow.state === 'pending'"> awaiting your approval</ng-container>
         </div>
       </ng-container>

+ 10 - 1
client/src/app/shared/users/user.model.ts

@@ -2,15 +2,18 @@ import { hasUserRight, User as UserServerModel, UserNotificationSetting, UserRig
 import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
 import { Account } from '@app/shared/account/account.model'
 import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
+import { UserAdminFlag } from '@shared/models/users/user-flag.model'
 
 export class User implements UserServerModel {
   id: number
   username: string
   email: string
   emailVerified: boolean
-  role: UserRole
   nsfwPolicy: NSFWPolicyType
 
+  role: UserRole
+  roleLabel: string
+
   webTorrentEnabled: boolean
   autoPlayVideo: boolean
   videosHistoryEnabled: boolean
@@ -21,6 +24,8 @@ export class User implements UserServerModel {
   videoChannels: VideoChannel[]
   createdAt: Date
 
+  adminFlags?: UserAdminFlag
+
   blocked: boolean
   blockedReason?: string
 
@@ -30,6 +35,7 @@ export class User implements UserServerModel {
     this.id = hash.id
     this.username = hash.username
     this.email = hash.email
+
     this.role = hash.role
 
     this.videoChannels = hash.videoChannels
@@ -40,6 +46,9 @@ export class User implements UserServerModel {
     this.videosHistoryEnabled = hash.videosHistoryEnabled
     this.autoPlayVideo = hash.autoPlayVideo
     this.createdAt = hash.createdAt
+
+    this.adminFlags = hash.adminFlags
+
     this.blocked = hash.blocked
     this.blockedReason = hash.blockedReason
 

+ 2 - 0
server.ts

@@ -255,6 +255,8 @@ async function startApplication () {
 
   // Make server listening
   server.listen(port, hostname, () => {
+    logger.debug('CONFIG', { CONFIG })
+
     logger.info('Server listening on %s:%d', hostname, port)
     logger.info('Web server: %s', WEBSERVER.URL)
   })

+ 6 - 3
server/controllers/api/users/index.ts

@@ -45,6 +45,7 @@ import { Notifier } from '../../../lib/notifier'
 import { mySubscriptionsRouter } from './my-subscriptions'
 import { CONFIG } from '../../../initializers/config'
 import { sequelizeTypescript } from '../../../initializers/database'
+import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
 
 const auditLogger = auditLoggerFactory('users')
 
@@ -175,7 +176,8 @@ async function createUser (req: express.Request, res: express.Response) {
     autoPlayVideo: true,
     role: body.role,
     videoQuota: body.videoQuota,
-    videoQuotaDaily: body.videoQuotaDaily
+    videoQuotaDaily: body.videoQuotaDaily,
+    adminFlags: body.adminFlags || UserAdminFlag.NONE
   })
 
   const { user, account } = await createUserAccountAndChannelAndPlaylist(userToCreate)
@@ -241,7 +243,7 @@ async function blockUser (req: express.Request, res: express.Response) {
 }
 
 function getUser (req: express.Request, res: express.Response) {
-  return res.json(res.locals.user.toFormattedJSON())
+  return res.json(res.locals.user.toFormattedJSON({ withAdminFlags: true }))
 }
 
 async function autocompleteUsers (req: express.Request, res: express.Response) {
@@ -253,7 +255,7 @@ async function autocompleteUsers (req: express.Request, res: express.Response) {
 async function listUsers (req: express.Request, res: express.Response) {
   const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.search)
 
-  return res.json(getFormattedObjects(resultList.data, resultList.total))
+  return res.json(getFormattedObjects(resultList.data, resultList.total, { withAdminFlags: true }))
 }
 
 async function removeUser (req: express.Request, res: express.Response) {
@@ -278,6 +280,7 @@ async function updateUser (req: express.Request, res: express.Response) {
   if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota
   if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily
   if (body.role !== undefined) userToUpdate.role = body.role
+  if (body.adminFlags !== undefined) userToUpdate.adminFlags = body.adminFlags
 
   const user = await userToUpdate.save()
 

+ 6 - 6
server/controllers/api/users/me.ts

@@ -129,7 +129,7 @@ async function getUserInformation (req: express.Request, res: express.Response)
   // We did not load channels in res.locals.user
   const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
 
-  return res.json(user.toFormattedJSON())
+  return res.json(user.toFormattedJSON({}))
 }
 
 async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) {
@@ -164,7 +164,7 @@ async function deleteMe (req: express.Request, res: express.Response) {
 
   await user.destroy()
 
-  auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
+  auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})))
 
   return res.sendStatus(204)
 }
@@ -173,7 +173,7 @@ async function updateMe (req: express.Request, res: express.Response) {
   const body: UserUpdateMe = req.body
 
   const user = res.locals.oauth.token.user
-  const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
+  const oldUserAuditView = new UserAuditView(user.toFormattedJSON({}))
 
   if (body.password !== undefined) user.password = body.password
   if (body.email !== undefined) user.email = body.email
@@ -193,7 +193,7 @@ async function updateMe (req: express.Request, res: express.Response) {
 
     await sendUpdateActor(userAccount, t)
 
-    auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
+    auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView)
   })
 
   return res.sendStatus(204)
@@ -202,13 +202,13 @@ async function updateMe (req: express.Request, res: express.Response) {
 async function updateMyAvatar (req: express.Request, res: express.Response) {
   const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
   const user = res.locals.oauth.token.user
-  const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
+  const oldUserAuditView = new UserAuditView(user.toFormattedJSON({}))
 
   const userAccount = await AccountModel.load(user.Account.id)
 
   const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount)
 
-  auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
+  auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView)
 
   return res.json({ avatar: avatar.toFormattedJSON() })
 }

+ 66 - 2
server/helpers/custom-jsonld-signature.ts

@@ -1,13 +1,77 @@
 import * as AsyncLRU from 'async-lru'
 import * as jsonld from 'jsonld'
 import * as jsig from 'jsonld-signatures'
+import { logger } from './logger'
+
+const CACHE = {
+  'https://w3id.org/security/v1': {
+    '@context': {
+      'id': '@id',
+      'type': '@type',
+
+      'dc': 'http://purl.org/dc/terms/',
+      'sec': 'https://w3id.org/security#',
+      'xsd': 'http://www.w3.org/2001/XMLSchema#',
+
+      'EcdsaKoblitzSignature2016': 'sec:EcdsaKoblitzSignature2016',
+      'Ed25519Signature2018': 'sec:Ed25519Signature2018',
+      'EncryptedMessage': 'sec:EncryptedMessage',
+      'GraphSignature2012': 'sec:GraphSignature2012',
+      'LinkedDataSignature2015': 'sec:LinkedDataSignature2015',
+      'LinkedDataSignature2016': 'sec:LinkedDataSignature2016',
+      'CryptographicKey': 'sec:Key',
+
+      'authenticationTag': 'sec:authenticationTag',
+      'canonicalizationAlgorithm': 'sec:canonicalizationAlgorithm',
+      'cipherAlgorithm': 'sec:cipherAlgorithm',
+      'cipherData': 'sec:cipherData',
+      'cipherKey': 'sec:cipherKey',
+      'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' },
+      'creator': { '@id': 'dc:creator', '@type': '@id' },
+      'digestAlgorithm': 'sec:digestAlgorithm',
+      'digestValue': 'sec:digestValue',
+      'domain': 'sec:domain',
+      'encryptionKey': 'sec:encryptionKey',
+      'expiration': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' },
+      'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' },
+      'initializationVector': 'sec:initializationVector',
+      'iterationCount': 'sec:iterationCount',
+      'nonce': 'sec:nonce',
+      'normalizationAlgorithm': 'sec:normalizationAlgorithm',
+      'owner': { '@id': 'sec:owner', '@type': '@id' },
+      'password': 'sec:password',
+      'privateKey': { '@id': 'sec:privateKey', '@type': '@id' },
+      'privateKeyPem': 'sec:privateKeyPem',
+      'publicKey': { '@id': 'sec:publicKey', '@type': '@id' },
+      'publicKeyBase58': 'sec:publicKeyBase58',
+      'publicKeyPem': 'sec:publicKeyPem',
+      'publicKeyWif': 'sec:publicKeyWif',
+      'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' },
+      'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' },
+      'salt': 'sec:salt',
+      'signature': 'sec:signature',
+      'signatureAlgorithm': 'sec:signingAlgorithm',
+      'signatureValue': 'sec:signatureValue'
+    }
+  }
+}
 
 const nodeDocumentLoader = jsonld.documentLoaders.node()
 
 const lru = new AsyncLRU({
   max: 10,
-  load: (key, cb) => {
-    nodeDocumentLoader(key, cb)
+  load: (url, cb) => {
+    if (CACHE[ url ] !== undefined) {
+      logger.debug('Using cache for JSON-LD %s.', url)
+
+      return cb(null, {
+        contextUrl: null,
+        document: CACHE[ url ],
+        documentUrl: url
+      })
+    }
+
+    nodeDocumentLoader(url, cb)
   }
 })
 

+ 7 - 1
server/helpers/custom-validators/users.ts

@@ -1,9 +1,10 @@
 import 'express-validator'
 import * as validator from 'validator'
-import { UserRole } from '../../../shared'
+import { UserNotificationSettingValue, UserRole } from '../../../shared'
 import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants'
 import { exists, isFileValid, isBooleanValid } from './misc'
 import { values } from 'lodash'
+import { UserAdminFlag } from '../../../shared/models/users/user-flag.model'
 
 const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
 
@@ -54,6 +55,10 @@ function isUserAutoPlayVideoValid (value: any) {
   return isBooleanValid(value)
 }
 
+function isUserAdminFlagsValid (value: any) {
+  return exists(value) && validator.isInt('' + value)
+}
+
 function isUserBlockedValid (value: any) {
   return isBooleanValid(value)
 }
@@ -85,6 +90,7 @@ export {
   isUserVideoQuotaValid,
   isUserVideoQuotaDailyValid,
   isUserUsernameValid,
+  isUserAdminFlagsValid,
   isUserEmailVerifiedValid,
   isUserNSFWPolicyValid,
   isUserWebTorrentEnabledValid,

+ 1 - 1
server/initializers/constants.ts

@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 360
+const LAST_MIGRATION_VERSION = 365
 
 // ---------------------------------------------------------------------------
 

+ 40 - 0
server/initializers/migrations/0365-user-admin-flags.ts

@@ -0,0 +1,40 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize,
+  db: any
+}): Promise<void> {
+  {
+    const data = {
+      type: Sequelize.INTEGER,
+      defaultValue: null,
+      allowNull: true
+    }
+    await utils.queryInterface.addColumn('user', 'adminFlags', data)
+  }
+
+  {
+    const query = 'UPDATE "user" SET "adminFlags" = 0'
+    await utils.sequelize.query(query)
+  }
+
+  {
+    const data = {
+      type: Sequelize.INTEGER,
+      defaultValue: null,
+      allowNull: false
+    }
+    await utils.queryInterface.changeColumn('user', 'adminFlags', data)
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}

+ 4 - 2
server/lib/video-blacklist.ts

@@ -1,15 +1,16 @@
 import * as sequelize from 'sequelize'
 import { CONFIG } from '../initializers/config'
-import { VideoBlacklistType, UserRight } from '../../shared/models'
+import { UserRight, VideoBlacklistType } from '../../shared/models'
 import { VideoBlacklistModel } from '../models/video/video-blacklist'
 import { UserModel } from '../models/account/user'
 import { VideoModel } from '../models/video/video'
 import { logger } from '../helpers/logger'
+import { UserAdminFlag } from '../../shared/models/users/user-flag.model'
 
 async function autoBlacklistVideoIfNeeded (video: VideoModel, user: UserModel, transaction: sequelize.Transaction) {
   if (!CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED) return false
 
-  if (user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) return false
+  if (user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) || user.hasAdminFlag(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST)) return false
 
   const sequelizeOptions = { transaction }
   const videoBlacklistToCreate = {
@@ -19,6 +20,7 @@ async function autoBlacklistVideoIfNeeded (video: VideoModel, user: UserModel, t
     type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
   }
   await VideoBlacklistModel.create(videoBlacklistToCreate, sequelizeOptions)
+
   logger.info('Video %s auto-blacklisted.', video.uuid)
 
   return true

+ 3 - 0
server/middlewares/validators/users.ts

@@ -5,6 +5,7 @@ import { body, param } from 'express-validator/check'
 import { omit } from 'lodash'
 import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
 import {
+  isUserAdminFlagsValid,
   isUserAutoPlayVideoValid,
   isUserBlockedReasonValid,
   isUserDescriptionValid,
@@ -32,6 +33,7 @@ const usersAddValidator = [
   body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
   body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
   body('role').custom(isUserRoleValid).withMessage('Should have a valid role'),
+  body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'),
 
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking usersAdd parameters', { parameters: omit(req.body, 'password') })
@@ -120,6 +122,7 @@ const usersUpdateValidator = [
   body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
   body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
   body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
+  body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'),
 
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking usersUpdate parameters', { parameters: req.body })

+ 17 - 1
server/models/account/user.ts

@@ -22,6 +22,7 @@ import {
 import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
 import { User, UserRole } from '../../../shared/models/users'
 import {
+  isUserAdminFlagsValid,
   isUserAutoPlayVideoValid,
   isUserBlockedReasonValid,
   isUserBlockedValid,
@@ -49,6 +50,7 @@ import { VideoModel } from '../video/video'
 import { ActorModel } from '../activitypub/actor'
 import { ActorFollowModel } from '../activitypub/actor-follow'
 import { VideoImportModel } from '../video/video-import'
+import { UserAdminFlag } from '../../../shared/models/users/user-flag.model'
 
 enum ScopeNames {
   WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
@@ -140,6 +142,12 @@ export class UserModel extends Model<UserModel> {
   @Column
   autoPlayVideo: boolean
 
+  @AllowNull(false)
+  @Default(UserAdminFlag.NONE)
+  @Is('UserAdminFlags', value => throwIfNotValid(value, isUserAdminFlagsValid, 'user admin flags'))
+  @Column
+  adminFlags?: UserAdminFlag
+
   @AllowNull(false)
   @Default(false)
   @Is('UserBlocked', value => throwIfNotValid(value, isUserBlockedValid, 'blocked boolean'))
@@ -516,11 +524,15 @@ export class UserModel extends Model<UserModel> {
     return hasUserRight(this.role, right)
   }
 
+  hasAdminFlag (flag: UserAdminFlag) {
+    return this.adminFlags & flag
+  }
+
   isPasswordMatch (password: string) {
     return comparePassword(password, this.password)
   }
 
-  toFormattedJSON (): User {
+  toFormattedJSON (parameters: { withAdminFlags?: boolean } = {}): User {
     const videoQuotaUsed = this.get('videoQuotaUsed')
     const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
 
@@ -551,6 +563,10 @@ export class UserModel extends Model<UserModel> {
             : undefined
     }
 
+    if (parameters.withAdminFlags) {
+      Object.assign(json, { adminFlags: this.adminFlags })
+    }
+
     if (Array.isArray(this.Account.VideoChannels) === true) {
       json.videoChannels = this.Account.VideoChannels
         .map(c => c.toFormattedJSON())

+ 1 - 1
server/tests/api/activitypub/fetch.ts

@@ -38,7 +38,7 @@ describe('Test ActivityPub fetcher', function () {
 
     const user = { username: 'user1', password: 'password' }
     for (const server of servers) {
-      await createUser(server.url, server.accessToken, user.username, user.password)
+      await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
     }
 
     const userAccessToken = await userLogin(servers[0], user)

+ 1 - 1
server/tests/api/check-params/blocklist.ts

@@ -36,7 +36,7 @@ describe('Test blocklist API validators', function () {
     server = servers[0]
 
     const user = { username: 'user1', password: 'password' }
-    await createUser(server.url, server.accessToken, user.username, user.password)
+    await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
 
     userAccessToken = await userLogin(server, user)
 

+ 1 - 1
server/tests/api/check-params/config.ts

@@ -110,7 +110,7 @@ describe('Test config API validators', function () {
       username: 'user1',
       password: 'password'
     }
-    await createUser(server.url, server.accessToken, user.username, user.password)
+    await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
     userAccessToken = await userLogin(server, user)
   })
 

+ 1 - 1
server/tests/api/check-params/debug.ts

@@ -33,7 +33,7 @@ describe('Test debug API validators', function () {
       username: 'user1',
       password: 'my super password'
     }
-    await createUser(server.url, server.accessToken, user.username, user.password)
+    await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
     userAccessToken = await userLogin(server, user)
   })
 

+ 1 - 1
server/tests/api/check-params/follows.ts

@@ -35,7 +35,7 @@ describe('Test server follows API validators', function () {
         password: 'password'
       }
 
-      await createUser(server.url, server.accessToken, user.username, user.password)
+      await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
       userAccessToken = await userLogin(server, user)
     })
 

+ 1 - 1
server/tests/api/check-params/jobs.ts

@@ -38,7 +38,7 @@ describe('Test jobs API validators', function () {
       username: 'user1',
       password: 'my super password'
     }
-    await createUser(server.url, server.accessToken, user.username, user.password)
+    await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
     userAccessToken = await userLogin(server, user)
   })
 

+ 1 - 1
server/tests/api/check-params/logs.ts

@@ -33,7 +33,7 @@ describe('Test logs API validators', function () {
       username: 'user1',
       password: 'my super password'
     }
-    await createUser(server.url, server.accessToken, user.username, user.password)
+    await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
     userAccessToken = await userLogin(server, user)
   })
 

+ 1 - 1
server/tests/api/check-params/redundancy.ts

@@ -34,7 +34,7 @@ describe('Test server redundancy API validators', function () {
       password: 'password'
     }
 
-    await createUser(servers[0].url, servers[0].accessToken, user.username, user.password)
+    await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password })
     userAccessToken = await userLogin(servers[0], user)
   })
 

+ 1 - 1
server/tests/api/check-params/user-subscriptions.ts

@@ -42,7 +42,7 @@ describe('Test user subscriptions API validators', function () {
       username: 'user1',
       password: 'my super password'
     }
-    await createUser(server.url, server.accessToken, user.username, user.password)
+    await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
     userAccessToken = await userLogin(server, user)
   })
 

+ 22 - 2
server/tests/api/check-params/users.ts

@@ -19,6 +19,7 @@ import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from
 import { VideoPrivacy } from '../../../../shared/models/videos'
 import { waitJobs } from '../../../../shared/utils/server/jobs'
 import { expect } from 'chai'
+import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
 
 describe('Test users API validators', function () {
   const path = '/api/v1/users/'
@@ -47,7 +48,13 @@ describe('Test users API validators', function () {
     await setAccessTokensToServers([ server ])
 
     const videoQuota = 42000000
-    await createUser(server.url, server.accessToken, user.username, user.password, videoQuota)
+    await createUser({
+      url: server.url,
+      accessToken: server.accessToken,
+      username: user.username,
+      password: user.password,
+      videoQuota: videoQuota
+    })
     userAccessToken = await userLogin(server, user)
 
     {
@@ -99,7 +106,8 @@ describe('Test users API validators', function () {
       password: 'my super password',
       videoQuota: -1,
       videoQuotaDaily: -1,
-      role: UserRole.USER
+      role: UserRole.USER,
+      adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST
     }
 
     it('Should fail with a too small username', async function () {
@@ -150,6 +158,12 @@ describe('Test users API validators', function () {
       await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
     })
 
+    it('Should fail with invalid admin flags', async function () {
+      const fields = immutableAssign(baseCorrectParams, { adminFlags: 'toto' })
+
+      await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
+    })
+
     it('Should fail with an non authenticated user', async function () {
       await makePostBodyRequest({
         url: server.url,
@@ -498,6 +512,12 @@ describe('Test users API validators', function () {
       await makePutBodyRequest({ url: server.url, path: path + rootId, token: server.accessToken, fields })
     })
 
+    it('Should fail with invalid admin flags', async function () {
+      const fields = { adminFlags: 'toto' }
+
+      await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
+    })
+
     it('Should succeed with the correct params', async function () {
       const fields = {
         email: 'email@example.com',

+ 1 - 1
server/tests/api/check-params/video-abuses.ts

@@ -41,7 +41,7 @@ describe('Test video abuses API validators', function () {
 
     const username = 'user1'
     const password = 'my super password'
-    await createUser(server.url, server.accessToken, username, password)
+    await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password })
     userAccessToken = await userLogin(server, { username, password })
 
     const res = await uploadVideo(server.url, server.accessToken, {})

+ 6 - 7
server/tests/api/check-params/video-blacklist.ts

@@ -8,7 +8,6 @@ import {
   flushAndRunMultipleServers,
   flushTests,
   getBlacklistedVideosList,
-  getBlacklistedVideosListWithTypeFilter,
   getVideo,
   getVideoWithToken,
   killallServers,
@@ -49,14 +48,14 @@ describe('Test video blacklist API validators', function () {
     {
       const username = 'user1'
       const password = 'my super password'
-      await createUser(servers[0].url, servers[0].accessToken, username, password)
+      await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: username, password: password })
       userAccessToken1 = await userLogin(servers[0], { username, password })
     }
 
     {
       const username = 'user2'
       const password = 'my super password'
-      await createUser(servers[0].url, servers[0].accessToken, username, password)
+      await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: username, password: password })
       userAccessToken2 = await userLogin(servers[0], { username, password })
     }
 
@@ -221,11 +220,11 @@ describe('Test video blacklist API validators', function () {
     const basePath = '/api/v1/videos/blacklist/'
 
     it('Should fail with a non authenticated user', async function () {
-      await getBlacklistedVideosList(servers[0].url, 'fake token', 401)
+      await getBlacklistedVideosList({ url: servers[0].url, token: 'fake token', specialStatus: 401 })
     })
 
     it('Should fail with a non admin user', async function () {
-      await getBlacklistedVideosList(servers[0].url, userAccessToken2, 403)
+      await getBlacklistedVideosList({ url: servers[0].url, token: userAccessToken2, specialStatus: 403 })
     })
 
     it('Should fail with a bad start pagination', async function () {
@@ -241,11 +240,11 @@ describe('Test video blacklist API validators', function () {
     })
 
     it('Should fail with an invalid type', async function () {
-      await getBlacklistedVideosListWithTypeFilter(servers[0].url, servers[0].accessToken, 0, 400)
+      await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, type: 0, specialStatus: 400 })
     })
 
     it('Should succeed with the correct parameters', async function () {
-      await getBlacklistedVideosListWithTypeFilter(servers[0].url, servers[0].accessToken, VideoBlacklistType.MANUAL)
+      await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, type: VideoBlacklistType.MANUAL })
     })
   })
 

+ 1 - 1
server/tests/api/check-params/video-captions.ts

@@ -45,7 +45,7 @@ describe('Test video captions API validator', function () {
         username: 'user1',
         password: 'my super password'
       }
-      await createUser(server.url, server.accessToken, user.username, user.password)
+      await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
       userAccessToken = await userLogin(server, user)
     }
   })

+ 1 - 1
server/tests/api/check-params/video-channels.ts

@@ -53,7 +53,7 @@ describe('Test video channels API validator', function () {
     }
 
     {
-      await createUser(server.url, server.accessToken, user.username, user.password)
+      await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
       accessTokenUser = await userLogin(server, user)
     }
   })

+ 1 - 1
server/tests/api/check-params/video-comments.ts

@@ -52,7 +52,7 @@ describe('Test video comments API validator', function () {
         username: 'user1',
         password: 'my super password'
       }
-      await createUser(server.url, server.accessToken, user.username, user.password)
+      await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
       userAccessToken = await userLogin(server, user)
     }
   })

+ 2 - 2
server/tests/api/check-params/video-imports.ts

@@ -46,7 +46,7 @@ describe('Test video imports API validator', function () {
 
     const username = 'user1'
     const password = 'my super password'
-    await createUser(server.url, server.accessToken, username, password)
+    await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password })
     userAccessToken = await userLogin(server, { username, password })
 
     {
@@ -167,7 +167,7 @@ describe('Test video imports API validator', function () {
         username: 'fake',
         password: 'fake_password'
       }
-      await createUser(server.url, server.accessToken, user.username, user.password)
+      await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
 
       const accessTokenUser = await userLogin(server, user)
       const res = await getMyUserInformation(server.url, accessTokenUser)

+ 10 - 8
server/tests/api/check-params/videos-filter.ts

@@ -56,18 +56,20 @@ describe('Test videos filters', function () {
     await setDefaultVideoChannel([ server ])
 
     const user = { username: 'user1', password: 'my super password' }
-    await createUser(server.url, server.accessToken, user.username, user.password)
+    await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
     userAccessToken = await userLogin(server, user)
 
     const moderator = { username: 'moderator', password: 'my super password' }
     await createUser(
-      server.url,
-      server.accessToken,
-      moderator.username,
-      moderator.password,
-      undefined,
-      undefined,
-      UserRole.MODERATOR
+      {
+        url: server.url,
+        accessToken: server.accessToken,
+        username: moderator.username,
+        password: moderator.password,
+        videoQuota: undefined,
+        videoQuotaDaily: undefined,
+        role: UserRole.MODERATOR
+      }
     )
     moderatorAccessToken = await userLogin(server, moderator)
 

+ 2 - 2
server/tests/api/check-params/videos.ts

@@ -41,7 +41,7 @@ describe('Test videos API validator', function () {
 
     const username = 'user1'
     const password = 'my super password'
-    await createUser(server.url, server.accessToken, username, password)
+    await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password })
     userAccessToken = await userLogin(server, { username, password })
 
     {
@@ -265,7 +265,7 @@ describe('Test videos API validator', function () {
         username: 'fake',
         password: 'fake_password'
       }
-      await createUser(server.url, server.accessToken, user.username, user.password)
+      await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
 
       const accessTokenUser = await userLogin(server, user)
       const res = await getMyUserInformation(server.url, accessTokenUser)

+ 7 - 1
server/tests/api/notifications/user-notifications.ts

@@ -133,7 +133,13 @@ describe('Test users notifications', function () {
       username: 'user_1',
       password: 'super password'
     }
-    await createUser(servers[0].url, servers[0].accessToken, user.username, user.password, 10 * 1000 * 1000)
+    await createUser({
+      url: servers[ 0 ].url,
+      accessToken: servers[ 0 ].accessToken,
+      username: user.username,
+      password: user.password,
+      videoQuota: 10 * 1000 * 1000
+    })
     userAccessToken = await userLogin(servers[0], user)
 
     await updateMyNotificationSettings(servers[0].url, userAccessToken, allNotificationSettings)

+ 2 - 2
server/tests/api/search/search-activitypub-video-channels.ts

@@ -40,7 +40,7 @@ describe('Test a ActivityPub video channels search', function () {
     await setAccessTokensToServers(servers)
 
     {
-      await createUser(servers[0].url, servers[0].accessToken, 'user1_server1', 'password')
+      await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: 'user1_server1', password: 'password' })
       const channel = {
         name: 'channel1_server1',
         displayName: 'Channel 1 server 1'
@@ -50,7 +50,7 @@ describe('Test a ActivityPub video channels search', function () {
 
     {
       const user = { username: 'user1_server2', password: 'password' }
-      await createUser(servers[1].url, servers[1].accessToken, user.username, user.password)
+      await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password })
       userServer2Token = await userLogin(servers[1], user)
 
       const channel = {

+ 1 - 1
server/tests/api/server/email.ts

@@ -54,7 +54,7 @@ describe('Test emails', function () {
     await setAccessTokensToServers([ server ])
 
     {
-      const res = await createUser(server.url, server.accessToken, user.username, user.password)
+      const res = await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
       userId = res.body.user.id
 
       userAccessToken = await userLogin(server, user)

+ 1 - 1
server/tests/api/server/follow-constraints.ts

@@ -47,7 +47,7 @@ describe('Test follow constraints', function () {
       username: 'user1',
       password: 'super_password'
     }
-    await createUser(servers[0].url, servers[0].accessToken, user.username, user.password)
+    await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password })
     userAccessToken = await userLogin(servers[0], user)
 
     await doubleFollow(servers[0], servers[1])

+ 1 - 1
server/tests/api/server/follows.ts

@@ -263,7 +263,7 @@ describe('Test follows', function () {
 
       {
         const user = { username: 'captain', password: 'password' }
-        await createUser(servers[ 2 ].url, servers[ 2 ].accessToken, user.username, user.password)
+        await createUser({ url: servers[ 2 ].url, accessToken: servers[ 2 ].accessToken, username: user.username, password: user.password })
         const userAccessToken = await userLogin(servers[ 2 ], user)
 
         const resVideos = await getVideosList(servers[ 2 ].url)

+ 1 - 1
server/tests/api/server/stats.ts

@@ -37,7 +37,7 @@ describe('Test stats (excluding redundancy)', function () {
       username: 'user1',
       password: 'super_password'
     }
-    await createUser(servers[0].url, servers[0].accessToken, user.username, user.password)
+    await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password })
 
     const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' })
     const videoUUID = resVideo.body.video.uuid

+ 3 - 3
server/tests/api/users/blocklist.ts

@@ -86,7 +86,7 @@ describe('Test blocklist', function () {
 
     {
       const user = { username: 'user1', password: 'password' }
-      await createUser(servers[0].url, servers[0].accessToken, user.username, user.password)
+      await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password })
 
       userToken1 = await userLogin(servers[0], user)
       await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' })
@@ -94,14 +94,14 @@ describe('Test blocklist', function () {
 
     {
       const user = { username: 'moderator', password: 'password' }
-      await createUser(servers[0].url, servers[0].accessToken, user.username, user.password)
+      await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password })
 
       userModeratorToken = await userLogin(servers[0], user)
     }
 
     {
       const user = { username: 'user2', password: 'password' }
-      await createUser(servers[1].url, servers[1].accessToken, user.username, user.password)
+      await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password })
 
       userToken2 = await userLogin(servers[1], user)
       await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' })

+ 1 - 1
server/tests/api/users/user-subscriptions.ts

@@ -45,7 +45,7 @@ describe('Test users subscriptions', function () {
     {
       for (const server of servers) {
         const user = { username: 'user' + server.serverNumber, password: 'password' }
-        await createUser(server.url, server.accessToken, user.username, user.password)
+        await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
 
         const accessToken = await userLogin(server, user)
         users.push({ accessToken })

+ 6 - 1
server/tests/api/users/users-multiple-servers.ts

@@ -57,7 +57,12 @@ describe('Test users with multiple servers', function () {
         username: 'user1',
         password: 'password'
       }
-      const res = await createUser(servers[ 0 ].url, servers[ 0 ].accessToken, user.username, user.password)
+      const res = await createUser({
+        url: servers[ 0 ].url,
+        accessToken: servers[ 0 ].accessToken,
+        username: user.username,
+        password: user.password
+      })
       userId = res.body.user.id
       userAccessToken = await userLogin(servers[ 0 ], user)
     }

+ 476 - 416
server/tests/api/users/users.ts

@@ -37,6 +37,7 @@ import {
 import { follow } from '../../../../shared/utils/server/follows'
 import { setAccessTokensToServers } from '../../../../shared/utils/users/login'
 import { getMyVideos } from '../../../../shared/utils/videos/videos'
+import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
 
 const expect = chai.expect
 
@@ -60,563 +61,622 @@ describe('Test users', function () {
     await setAccessTokensToServers([ server ])
   })
 
-  it('Should create a new client')
+  describe('OAuth client', function () {
+    it('Should create a new client')
 
-  it('Should return the first client')
+    it('Should return the first client')
 
-  it('Should remove the last client')
+    it('Should remove the last client')
 
-  it('Should not login with an invalid client id', async function () {
-    const client = { id: 'client', secret: server.client.secret }
-    const res = await login(server.url, client, server.user, 400)
+    it('Should not login with an invalid client id', async function () {
+      const client = { id: 'client', secret: server.client.secret }
+      const res = await login(server.url, client, server.user, 400)
 
-    expect(res.body.error).to.contain('client is invalid')
-  })
+      expect(res.body.error).to.contain('client is invalid')
+    })
 
-  it('Should not login with an invalid client secret', async function () {
-    const client = { id: server.client.id, secret: 'coucou' }
-    const res = await login(server.url, client, server.user, 400)
+    it('Should not login with an invalid client secret', async function () {
+      const client = { id: server.client.id, secret: 'coucou' }
+      const res = await login(server.url, client, server.user, 400)
 
-    expect(res.body.error).to.contain('client is invalid')
+      expect(res.body.error).to.contain('client is invalid')
+    })
   })
 
-  it('Should not login with an invalid username', async function () {
-    const user = { username: 'captain crochet', password: server.user.password }
-    const res = await login(server.url, server.client, user, 400)
-
-    expect(res.body.error).to.contain('credentials are invalid')
-  })
+  describe('Login', function () {
 
-  it('Should not login with an invalid password', async function () {
-    const user = { username: server.user.username, password: 'mew_three' }
-    const res = await login(server.url, server.client, user, 400)
+    it('Should not login with an invalid username', async function () {
+      const user = { username: 'captain crochet', password: server.user.password }
+      const res = await login(server.url, server.client, user, 400)
 
-    expect(res.body.error).to.contain('credentials are invalid')
-  })
+      expect(res.body.error).to.contain('credentials are invalid')
+    })
 
-  it('Should not be able to upload a video', async function () {
-    accessToken = 'my_super_token'
+    it('Should not login with an invalid password', async function () {
+      const user = { username: server.user.username, password: 'mew_three' }
+      const res = await login(server.url, server.client, user, 400)
 
-    const videoAttributes = {}
-    await uploadVideo(server.url, accessToken, videoAttributes, 401)
-  })
+      expect(res.body.error).to.contain('credentials are invalid')
+    })
 
-  it('Should not be able to follow', async function () {
-    accessToken = 'my_super_token'
-    await follow(server.url, [ 'http://example.com' ], accessToken, 401)
-  })
+    it('Should not be able to upload a video', async function () {
+      accessToken = 'my_super_token'
 
-  it('Should not be able to unfollow')
+      const videoAttributes = {}
+      await uploadVideo(server.url, accessToken, videoAttributes, 401)
+    })
 
-  it('Should be able to login', async function () {
-    const res = await login(server.url, server.client, server.user, 200)
+    it('Should not be able to follow', async function () {
+      accessToken = 'my_super_token'
+      await follow(server.url, [ 'http://example.com' ], accessToken, 401)
+    })
 
-    accessToken = res.body.access_token
-  })
+    it('Should not be able to unfollow')
 
-  it('Should upload the video with the correct token', async function () {
-    const videoAttributes = {}
-    await uploadVideo(server.url, accessToken, videoAttributes)
-    const res = await getVideosList(server.url)
-    const video = res.body.data[ 0 ]
+    it('Should be able to login', async function () {
+      const res = await login(server.url, server.client, server.user, 200)
 
-    expect(video.account.name).to.equal('root')
-    videoId = video.id
+      accessToken = res.body.access_token
+    })
   })
 
-  it('Should upload the video again with the correct token', async function () {
-    const videoAttributes = {}
-    await uploadVideo(server.url, accessToken, videoAttributes)
-  })
+  describe('Upload', function () {
 
-  it('Should retrieve a video rating', async function () {
-    await rateVideo(server.url, accessToken, videoId, 'like')
-    const res = await getMyUserVideoRating(server.url, accessToken, videoId)
-    const rating = res.body
+    it('Should upload the video with the correct token', async function () {
+      const videoAttributes = {}
+      await uploadVideo(server.url, accessToken, videoAttributes)
+      const res = await getVideosList(server.url)
+      const video = res.body.data[ 0 ]
 
-    expect(rating.videoId).to.equal(videoId)
-    expect(rating.rating).to.equal('like')
+      expect(video.account.name).to.equal('root')
+      videoId = video.id
+    })
+
+    it('Should upload the video again with the correct token', async function () {
+      const videoAttributes = {}
+      await uploadVideo(server.url, accessToken, videoAttributes)
+    })
   })
 
-  it('Should retrieve ratings list', async function () {
-    await rateVideo(server.url, accessToken, videoId, 'like')
+  describe('Ratings', function () {
 
-    const res = await getAccountRatings(server.url, server.user.username, server.accessToken, null, 200)
-    const ratings = res.body
+    it('Should retrieve a video rating', async function () {
+      await rateVideo(server.url, accessToken, videoId, 'like')
+      const res = await getMyUserVideoRating(server.url, accessToken, videoId)
+      const rating = res.body
 
-    expect(ratings.total).to.equal(1)
-    expect(ratings.data[0].video.id).to.equal(videoId)
-    expect(ratings.data[0].rating).to.equal('like')
-  })
+      expect(rating.videoId).to.equal(videoId)
+      expect(rating.rating).to.equal('like')
+    })
 
-  it('Should retrieve ratings list by rating type', async function () {
-    {
-      const res = await getAccountRatings(server.url, server.user.username, server.accessToken, 'like')
-      const ratings = res.body
-      expect(ratings.data.length).to.equal(1)
-    }
+    it('Should retrieve ratings list', async function () {
+      await rateVideo(server.url, accessToken, videoId, 'like')
 
-    {
-      const res = await getAccountRatings(server.url, server.user.username, server.accessToken, 'dislike')
+      const res = await getAccountRatings(server.url, server.user.username, server.accessToken, null, 200)
       const ratings = res.body
-      expect(ratings.data.length).to.equal(0)
-    }
-  })
-
-  it('Should not be able to remove the video with an incorrect token', async function () {
-    await removeVideo(server.url, 'bad_token', videoId, 401)
-  })
 
-  it('Should not be able to remove the video with the token of another account')
+      expect(ratings.total).to.equal(1)
+      expect(ratings.data[ 0 ].video.id).to.equal(videoId)
+      expect(ratings.data[ 0 ].rating).to.equal('like')
+    })
 
-  it('Should be able to remove the video with the correct token', async function () {
-    await removeVideo(server.url, accessToken, videoId)
+    it('Should retrieve ratings list by rating type', async function () {
+      {
+        const res = await getAccountRatings(server.url, server.user.username, server.accessToken, 'like')
+        const ratings = res.body
+        expect(ratings.data.length).to.equal(1)
+      }
+
+      {
+        const res = await getAccountRatings(server.url, server.user.username, server.accessToken, 'dislike')
+        const ratings = res.body
+        expect(ratings.data.length).to.equal(0)
+      }
+    })
   })
 
-  it('Should logout (revoke token)')
-
-  it('Should not be able to get the user information')
-
-  it('Should not be able to upload a video')
-
-  it('Should not be able to remove a video')
+  describe('Remove video', function () {
+    it('Should not be able to remove the video with an incorrect token', async function () {
+      await removeVideo(server.url, 'bad_token', videoId, 401)
+    })
 
-  it('Should not be able to rate a video', async function () {
-    const path = '/api/v1/videos/'
-    const data = {
-      rating: 'likes'
-    }
+    it('Should not be able to remove the video with the token of another account')
 
-    const options = {
-      url: server.url,
-      path: path + videoId,
-      token: 'wrong token',
-      fields: data,
-      statusCodeExpected: 401
-    }
-    await makePutBodyRequest(options)
+    it('Should be able to remove the video with the correct token', async function () {
+      await removeVideo(server.url, accessToken, videoId)
+    })
   })
 
-  it('Should be able to login again')
+  describe('Logout', function () {
+    it('Should logout (revoke token)')
 
-  it('Should have an expired access token')
+    it('Should not be able to get the user information')
 
-  it('Should refresh the token')
+    it('Should not be able to upload a video')
 
-  it('Should be able to upload a video again')
+    it('Should not be able to remove a video')
 
-  it('Should be able to create a new user', async function () {
-    await createUser(server.url, accessToken, user.username, user.password, 2 * 1024 * 1024)
-  })
+    it('Should not be able to rate a video', async function () {
+      const path = '/api/v1/videos/'
+      const data = {
+        rating: 'likes'
+      }
 
-  it('Should be able to login with this user', async function () {
-    accessTokenUser = await userLogin(server, user)
-  })
+      const options = {
+        url: server.url,
+        path: path + videoId,
+        token: 'wrong token',
+        fields: data,
+        statusCodeExpected: 401
+      }
+      await makePutBodyRequest(options)
+    })
 
-  it('Should be able to get the user information', async function () {
-    const res = await getMyUserInformation(server.url, accessTokenUser)
-    const user = res.body
-
-    expect(user.username).to.equal('user_1')
-    expect(user.email).to.equal('user_1@example.com')
-    expect(user.nsfwPolicy).to.equal('display')
-    expect(user.videoQuota).to.equal(2 * 1024 * 1024)
-    expect(user.roleLabel).to.equal('User')
-    expect(user.id).to.be.a('number')
-    expect(user.account.displayName).to.equal('user_1')
-    expect(user.account.description).to.be.null
-  })
+    it('Should be able to login again')
 
-  it('Should be able to upload a video with this user', async function () {
-    this.timeout(5000)
+    it('Should have an expired access token')
 
-    const videoAttributes = {
-      name: 'super user video',
-      fixture: 'video_short.webm'
-    }
-    await uploadVideo(server.url, accessTokenUser, videoAttributes)
+    it('Should refresh the token')
+
+    it('Should be able to upload a video again')
   })
 
-  it('Should have video quota updated', async function () {
-    const res = await getMyUserVideoQuotaUsed(server.url, accessTokenUser)
-    const data = res.body
+  describe('Creating a user', function () {
 
-    expect(data.videoQuotaUsed).to.equal(218910)
+    it('Should be able to create a new user', async function () {
+      await createUser({
+        url: server.url,
+        accessToken: accessToken,
+        username: user.username,
+        password: user.password,
+        videoQuota: 2 * 1024 * 1024,
+        adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST
+      })
+    })
 
-    const resUsers = await getUsersList(server.url, server.accessToken)
+    it('Should be able to login with this user', async function () {
+      accessTokenUser = await userLogin(server, user)
+    })
 
-    const users: User[] = resUsers.body.data
-    const tmpUser = users.find(u => u.username === user.username)
-    expect(tmpUser.videoQuotaUsed).to.equal(218910)
+    it('Should be able to get user information', async function () {
+      const res1 = await getMyUserInformation(server.url, accessTokenUser)
+      const userMe: User = res1.body
+
+      const res2 = await getUserInformation(server.url, server.accessToken, userMe.id)
+      const userGet: User = res2.body
+
+      for (const user of [ userMe, userGet ]) {
+        expect(user.username).to.equal('user_1')
+        expect(user.email).to.equal('user_1@example.com')
+        expect(user.nsfwPolicy).to.equal('display')
+        expect(user.videoQuota).to.equal(2 * 1024 * 1024)
+        expect(user.roleLabel).to.equal('User')
+        expect(user.id).to.be.a('number')
+        expect(user.account.displayName).to.equal('user_1')
+        expect(user.account.description).to.be.null
+      }
+
+      expect(userMe.adminFlags).to.be.undefined
+      expect(userGet.adminFlags).to.equal(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST)
+    })
   })
 
-  it('Should be able to list my videos', async function () {
-    const res = await getMyVideos(server.url, accessTokenUser, 0, 5)
-    expect(res.body.total).to.equal(1)
-
-    const videos = res.body.data
-    expect(videos).to.have.lengthOf(1)
+  describe('My videos & quotas', function () {
 
-    expect(videos[ 0 ].name).to.equal('super user video')
-  })
-
-  it('Should list all the users', async function () {
-    const res = await getUsersList(server.url, server.accessToken)
-    const result = res.body
-    const total = result.total
-    const users = result.data
+    it('Should be able to upload a video with this user', async function () {
+      this.timeout(5000)
 
-    expect(total).to.equal(2)
-    expect(users).to.be.an('array')
-    expect(users.length).to.equal(2)
+      const videoAttributes = {
+        name: 'super user video',
+        fixture: 'video_short.webm'
+      }
+      await uploadVideo(server.url, accessTokenUser, videoAttributes)
+    })
 
-    const user = users[ 0 ]
-    expect(user.username).to.equal('user_1')
-    expect(user.email).to.equal('user_1@example.com')
-    expect(user.nsfwPolicy).to.equal('display')
+    it('Should have video quota updated', async function () {
+      const res = await getMyUserVideoQuotaUsed(server.url, accessTokenUser)
+      const data = res.body
 
-    const rootUser = users[ 1 ]
-    expect(rootUser.username).to.equal('root')
-    expect(rootUser.email).to.equal('admin1@example.com')
-    expect(user.nsfwPolicy).to.equal('display')
+      expect(data.videoQuotaUsed).to.equal(218910)
 
-    userId = user.id
-  })
+      const resUsers = await getUsersList(server.url, server.accessToken)
 
-  it('Should list only the first user by username asc', async function () {
-    const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, 'username')
+      const users: User[] = resUsers.body.data
+      const tmpUser = users.find(u => u.username === user.username)
+      expect(tmpUser.videoQuotaUsed).to.equal(218910)
+    })
 
-    const result = res.body
-    const total = result.total
-    const users = result.data
+    it('Should be able to list my videos', async function () {
+      const res = await getMyVideos(server.url, accessTokenUser, 0, 5)
+      expect(res.body.total).to.equal(1)
 
-    expect(total).to.equal(2)
-    expect(users.length).to.equal(1)
+      const videos = res.body.data
+      expect(videos).to.have.lengthOf(1)
 
-    const user = users[ 0 ]
-    expect(user.username).to.equal('root')
-    expect(user.email).to.equal('admin1@example.com')
-    expect(user.roleLabel).to.equal('Administrator')
-    expect(user.nsfwPolicy).to.equal('display')
+      expect(videos[ 0 ].name).to.equal('super user video')
+    })
   })
 
-  it('Should list only the first user by username desc', async function () {
-    const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-username')
-    const result = res.body
-    const total = result.total
-    const users = result.data
+  describe('Users listing', function () {
 
-    expect(total).to.equal(2)
-    expect(users.length).to.equal(1)
+    it('Should list all the users', async function () {
+      const res = await getUsersList(server.url, server.accessToken)
+      const result = res.body
+      const total = result.total
+      const users = result.data
 
-    const user = users[ 0 ]
-    expect(user.username).to.equal('user_1')
-    expect(user.email).to.equal('user_1@example.com')
-    expect(user.nsfwPolicy).to.equal('display')
-  })
+      expect(total).to.equal(2)
+      expect(users).to.be.an('array')
+      expect(users.length).to.equal(2)
 
-  it('Should list only the second user by createdAt desc', async function () {
-    const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-createdAt')
-    const result = res.body
-    const total = result.total
-    const users = result.data
+      const user = users[ 0 ]
+      expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('user_1@example.com')
+      expect(user.nsfwPolicy).to.equal('display')
 
-    expect(total).to.equal(2)
-    expect(users.length).to.equal(1)
+      const rootUser = users[ 1 ]
+      expect(rootUser.username).to.equal('root')
+      expect(rootUser.email).to.equal('admin1@example.com')
+      expect(user.nsfwPolicy).to.equal('display')
 
-    const user = users[ 0 ]
-    expect(user.username).to.equal('user_1')
-    expect(user.email).to.equal('user_1@example.com')
-    expect(user.nsfwPolicy).to.equal('display')
-  })
+      userId = user.id
+    })
 
-  it('Should list all the users by createdAt asc', async function () {
-    const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt')
-    const result = res.body
-    const total = result.total
-    const users = result.data
+    it('Should list only the first user by username asc', async function () {
+      const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, 'username')
 
-    expect(total).to.equal(2)
-    expect(users.length).to.equal(2)
+      const result = res.body
+      const total = result.total
+      const users = result.data
 
-    expect(users[ 0 ].username).to.equal('root')
-    expect(users[ 0 ].email).to.equal('admin1@example.com')
-    expect(users[ 0 ].nsfwPolicy).to.equal('display')
+      expect(total).to.equal(2)
+      expect(users.length).to.equal(1)
 
-    expect(users[ 1 ].username).to.equal('user_1')
-    expect(users[ 1 ].email).to.equal('user_1@example.com')
-    expect(users[ 1 ].nsfwPolicy).to.equal('display')
-  })
+      const user = users[ 0 ]
+      expect(user.username).to.equal('root')
+      expect(user.email).to.equal('admin1@example.com')
+      expect(user.roleLabel).to.equal('Administrator')
+      expect(user.nsfwPolicy).to.equal('display')
+    })
 
-  it('Should search user by username', async function () {
-    const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'oot')
-    const users = res.body.data as User[]
+    it('Should list only the first user by username desc', async function () {
+      const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-username')
+      const result = res.body
+      const total = result.total
+      const users = result.data
 
-    expect(res.body.total).to.equal(1)
-    expect(users.length).to.equal(1)
+      expect(total).to.equal(2)
+      expect(users.length).to.equal(1)
 
-    expect(users[ 0 ].username).to.equal('root')
-  })
+      const user = users[ 0 ]
+      expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('user_1@example.com')
+      expect(user.nsfwPolicy).to.equal('display')
+    })
 
-  it('Should search user by email', async function () {
-    {
-      const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'r_1@exam')
-      const users = res.body.data as User[]
+    it('Should list only the second user by createdAt desc', async function () {
+      const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-createdAt')
+      const result = res.body
+      const total = result.total
+      const users = result.data
 
-      expect(res.body.total).to.equal(1)
+      expect(total).to.equal(2)
       expect(users.length).to.equal(1)
 
-      expect(users[ 0 ].username).to.equal('user_1')
-      expect(users[ 0 ].email).to.equal('user_1@example.com')
-    }
+      const user = users[ 0 ]
+      expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('user_1@example.com')
+      expect(user.nsfwPolicy).to.equal('display')
+    })
 
-    {
-      const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'example')
-      const users = res.body.data as User[]
+    it('Should list all the users by createdAt asc', async function () {
+      const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt')
+      const result = res.body
+      const total = result.total
+      const users = result.data
 
-      expect(res.body.total).to.equal(2)
+      expect(total).to.equal(2)
       expect(users.length).to.equal(2)
 
       expect(users[ 0 ].username).to.equal('root')
-      expect(users[ 1 ].username).to.equal('user_1')
-    }
-  })
+      expect(users[ 0 ].email).to.equal('admin1@example.com')
+      expect(users[ 0 ].nsfwPolicy).to.equal('display')
 
-  it('Should update my password', async function () {
-    await updateMyUser({
-      url: server.url,
-      accessToken: accessTokenUser,
-      currentPassword: 'super password',
-      newPassword: 'new password'
+      expect(users[ 1 ].username).to.equal('user_1')
+      expect(users[ 1 ].email).to.equal('user_1@example.com')
+      expect(users[ 1 ].nsfwPolicy).to.equal('display')
     })
-    user.password = 'new password'
 
-    await userLogin(server, user, 200)
-  })
+    it('Should search user by username', async function () {
+      const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'oot')
+      const users = res.body.data as User[]
+
+      expect(res.body.total).to.equal(1)
+      expect(users.length).to.equal(1)
 
-  it('Should be able to change the NSFW display attribute', async function () {
-    await updateMyUser({
-      url: server.url,
-      accessToken: accessTokenUser,
-      nsfwPolicy: 'do_not_list'
+      expect(users[ 0 ].username).to.equal('root')
     })
 
-    const res = await getMyUserInformation(server.url, accessTokenUser)
-    const user = res.body
+    it('Should search user by email', async function () {
+      {
+        const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'r_1@exam')
+        const users = res.body.data as User[]
 
-    expect(user.username).to.equal('user_1')
-    expect(user.email).to.equal('user_1@example.com')
-    expect(user.nsfwPolicy).to.equal('do_not_list')
-    expect(user.videoQuota).to.equal(2 * 1024 * 1024)
-    expect(user.id).to.be.a('number')
-    expect(user.account.displayName).to.equal('user_1')
-    expect(user.account.description).to.be.null
-  })
+        expect(res.body.total).to.equal(1)
+        expect(users.length).to.equal(1)
 
-  it('Should be able to change the autoPlayVideo attribute', async function () {
-    await updateMyUser({
-      url: server.url,
-      accessToken: accessTokenUser,
-      autoPlayVideo: false
-    })
+        expect(users[ 0 ].username).to.equal('user_1')
+        expect(users[ 0 ].email).to.equal('user_1@example.com')
+      }
 
-    const res = await getMyUserInformation(server.url, accessTokenUser)
-    const user = res.body
+      {
+        const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'example')
+        const users = res.body.data as User[]
 
-    expect(user.autoPlayVideo).to.be.false
-  })
+        expect(res.body.total).to.equal(2)
+        expect(users.length).to.equal(2)
 
-  it('Should be able to change the email display attribute', async function () {
-    await updateMyUser({
-      url: server.url,
-      accessToken: accessTokenUser,
-      email: 'updated@example.com'
+        expect(users[ 0 ].username).to.equal('root')
+        expect(users[ 1 ].username).to.equal('user_1')
+      }
     })
-
-    const res = await getMyUserInformation(server.url, accessTokenUser)
-    const user = res.body
-
-    expect(user.username).to.equal('user_1')
-    expect(user.email).to.equal('updated@example.com')
-    expect(user.nsfwPolicy).to.equal('do_not_list')
-    expect(user.videoQuota).to.equal(2 * 1024 * 1024)
-    expect(user.id).to.be.a('number')
-    expect(user.account.displayName).to.equal('user_1')
-    expect(user.account.description).to.be.null
   })
 
-  it('Should be able to update my avatar', async function () {
-    const fixture = 'avatar.png'
+  describe('Update my account', function () {
+    it('Should update my password', async function () {
+      await updateMyUser({
+        url: server.url,
+        accessToken: accessTokenUser,
+        currentPassword: 'super password',
+        newPassword: 'new password'
+      })
+      user.password = 'new password'
 
-    await updateMyAvatar({
-      url: server.url,
-      accessToken: accessTokenUser,
-      fixture
+      await userLogin(server, user, 200)
     })
 
-    const res = await getMyUserInformation(server.url, accessTokenUser)
-    const user = res.body
+    it('Should be able to change the NSFW display attribute', async function () {
+      await updateMyUser({
+        url: server.url,
+        accessToken: accessTokenUser,
+        nsfwPolicy: 'do_not_list'
+      })
+
+      const res = await getMyUserInformation(server.url, accessTokenUser)
+      const user = res.body
+
+      expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('user_1@example.com')
+      expect(user.nsfwPolicy).to.equal('do_not_list')
+      expect(user.videoQuota).to.equal(2 * 1024 * 1024)
+      expect(user.id).to.be.a('number')
+      expect(user.account.displayName).to.equal('user_1')
+      expect(user.account.description).to.be.null
+    })
 
-    await testImage(server.url, 'avatar-resized', user.account.avatar.path, '.png')
-  })
+    it('Should be able to change the autoPlayVideo attribute', async function () {
+      await updateMyUser({
+        url: server.url,
+        accessToken: accessTokenUser,
+        autoPlayVideo: false
+      })
 
-  it('Should be able to update my display name', async function () {
-    await updateMyUser({
-      url: server.url,
-      accessToken: accessTokenUser,
-      displayName: 'new display name'
+      const res = await getMyUserInformation(server.url, accessTokenUser)
+      const user = res.body
+
+      expect(user.autoPlayVideo).to.be.false
     })
 
-    const res = await getMyUserInformation(server.url, accessTokenUser)
-    const user = res.body
+    it('Should be able to change the email display attribute', async function () {
+      await updateMyUser({
+        url: server.url,
+        accessToken: accessTokenUser,
+        email: 'updated@example.com'
+      })
+
+      const res = await getMyUserInformation(server.url, accessTokenUser)
+      const user = res.body
+
+      expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('updated@example.com')
+      expect(user.nsfwPolicy).to.equal('do_not_list')
+      expect(user.videoQuota).to.equal(2 * 1024 * 1024)
+      expect(user.id).to.be.a('number')
+      expect(user.account.displayName).to.equal('user_1')
+      expect(user.account.description).to.be.null
+    })
 
-    expect(user.username).to.equal('user_1')
-    expect(user.email).to.equal('updated@example.com')
-    expect(user.nsfwPolicy).to.equal('do_not_list')
-    expect(user.videoQuota).to.equal(2 * 1024 * 1024)
-    expect(user.id).to.be.a('number')
-    expect(user.account.displayName).to.equal('new display name')
-    expect(user.account.description).to.be.null
-  })
+    it('Should be able to update my avatar', async function () {
+      const fixture = 'avatar.png'
 
-  it('Should be able to update my description', async function () {
-    await updateMyUser({
-      url: server.url,
-      accessToken: accessTokenUser,
-      description: 'my super description updated'
-    })
+      await updateMyAvatar({
+        url: server.url,
+        accessToken: accessTokenUser,
+        fixture
+      })
 
-    const res = await getMyUserInformation(server.url, accessTokenUser)
-    const user = res.body
+      const res = await getMyUserInformation(server.url, accessTokenUser)
+      const user = res.body
 
-    expect(user.username).to.equal('user_1')
-    expect(user.email).to.equal('updated@example.com')
-    expect(user.nsfwPolicy).to.equal('do_not_list')
-    expect(user.videoQuota).to.equal(2 * 1024 * 1024)
-    expect(user.id).to.be.a('number')
-    expect(user.account.displayName).to.equal('new display name')
-    expect(user.account.description).to.equal('my super description updated')
-  })
+      await testImage(server.url, 'avatar-resized', user.account.avatar.path, '.png')
+    })
+
+    it('Should be able to update my display name', async function () {
+      await updateMyUser({
+        url: server.url,
+        accessToken: accessTokenUser,
+        displayName: 'new display name'
+      })
+
+      const res = await getMyUserInformation(server.url, accessTokenUser)
+      const user = res.body
+
+      expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('updated@example.com')
+      expect(user.nsfwPolicy).to.equal('do_not_list')
+      expect(user.videoQuota).to.equal(2 * 1024 * 1024)
+      expect(user.id).to.be.a('number')
+      expect(user.account.displayName).to.equal('new display name')
+      expect(user.account.description).to.be.null
+    })
 
-  it('Should be able to update another user', async function () {
-    await updateUser({
-      url: server.url,
-      userId,
-      accessToken,
-      email: 'updated2@example.com',
-      emailVerified: true,
-      videoQuota: 42,
-      role: UserRole.MODERATOR
-    })
-
-    const res = await getUserInformation(server.url, accessToken, userId)
-    const user = res.body
-
-    expect(user.username).to.equal('user_1')
-    expect(user.email).to.equal('updated2@example.com')
-    expect(user.emailVerified).to.be.true
-    expect(user.nsfwPolicy).to.equal('do_not_list')
-    expect(user.videoQuota).to.equal(42)
-    expect(user.roleLabel).to.equal('Moderator')
-    expect(user.id).to.be.a('number')
+    it('Should be able to update my description', async function () {
+      await updateMyUser({
+        url: server.url,
+        accessToken: accessTokenUser,
+        description: 'my super description updated'
+      })
+
+      const res = await getMyUserInformation(server.url, accessTokenUser)
+      const user = res.body
+
+      expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('updated@example.com')
+      expect(user.nsfwPolicy).to.equal('do_not_list')
+      expect(user.videoQuota).to.equal(2 * 1024 * 1024)
+      expect(user.id).to.be.a('number')
+      expect(user.account.displayName).to.equal('new display name')
+      expect(user.account.description).to.equal('my super description updated')
+    })
   })
 
-  it('Should have removed the user token', async function () {
-    await getMyUserVideoQuotaUsed(server.url, accessTokenUser, 401)
+  describe('Updating another user', function () {
+
+    it('Should be able to update another user', async function () {
+      await updateUser({
+        url: server.url,
+        userId,
+        accessToken,
+        email: 'updated2@example.com',
+        emailVerified: true,
+        videoQuota: 42,
+        role: UserRole.MODERATOR,
+        adminFlags: UserAdminFlag.NONE
+      })
+
+      const res = await getUserInformation(server.url, accessToken, userId)
+      const user = res.body
+
+      expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('updated2@example.com')
+      expect(user.emailVerified).to.be.true
+      expect(user.nsfwPolicy).to.equal('do_not_list')
+      expect(user.videoQuota).to.equal(42)
+      expect(user.roleLabel).to.equal('Moderator')
+      expect(user.id).to.be.a('number')
+      expect(user.adminFlags).to.equal(UserAdminFlag.NONE)
+    })
 
-    accessTokenUser = await userLogin(server, user)
-  })
+    it('Should have removed the user token', async function () {
+      await getMyUserVideoQuotaUsed(server.url, accessTokenUser, 401)
 
-  it('Should be able to update another user password', async function () {
-    await updateUser({
-      url: server.url,
-      userId,
-      accessToken,
-      password: 'password updated'
+      accessTokenUser = await userLogin(server, user)
     })
 
-    await getMyUserVideoQuotaUsed(server.url, accessTokenUser, 401)
+    it('Should be able to update another user password', async function () {
+      await updateUser({
+        url: server.url,
+        userId,
+        accessToken,
+        password: 'password updated'
+      })
 
-    await userLogin(server, user, 400)
+      await getMyUserVideoQuotaUsed(server.url, accessTokenUser, 401)
 
-    user.password = 'password updated'
-    accessTokenUser = await userLogin(server, user)
-  })
+      await userLogin(server, user, 400)
 
-  it('Should be able to list video blacklist by a moderator', async function () {
-    await getBlacklistedVideosList(server.url, accessTokenUser)
+      user.password = 'password updated'
+      accessTokenUser = await userLogin(server, user)
+    })
   })
 
-  it('Should be able to remove this user', async function () {
-    await removeUser(server.url, userId, accessToken)
+  describe('Video blacklists', function () {
+    it('Should be able to list video blacklist by a moderator', async function () {
+      await getBlacklistedVideosList({ url: server.url, token: accessTokenUser })
+    })
   })
 
-  it('Should not be able to login with this user', async function () {
-    await userLogin(server, user, 400)
-  })
+  describe('Remove a user', function () {
+    it('Should be able to remove this user', async function () {
+      await removeUser(server.url, userId, accessToken)
+    })
 
-  it('Should not have videos of this user', async function () {
-    const res = await getVideosList(server.url)
+    it('Should not be able to login with this user', async function () {
+      await userLogin(server, user, 400)
+    })
 
-    expect(res.body.total).to.equal(1)
+    it('Should not have videos of this user', async function () {
+      const res = await getVideosList(server.url)
 
-    const video = res.body.data[ 0 ]
-    expect(video.account.name).to.equal('root')
-  })
+      expect(res.body.total).to.equal(1)
 
-  it('Should register a new user', async function () {
-    await registerUser(server.url, 'user_15', 'my super password')
+      const video = res.body.data[ 0 ]
+      expect(video.account.name).to.equal('root')
+    })
   })
 
-  it('Should be able to login with this registered user', async function () {
-    const user15 = {
-      username: 'user_15',
-      password: 'my super password'
-    }
+  describe('Registering a new user', function () {
+    it('Should register a new user', async function () {
+      await registerUser(server.url, 'user_15', 'my super password')
+    })
 
-    accessToken = await userLogin(server, user15)
-  })
+    it('Should be able to login with this registered user', async function () {
+      const user15 = {
+        username: 'user_15',
+        password: 'my super password'
+      }
 
-  it('Should have the correct video quota', async function () {
-    const res = await getMyUserInformation(server.url, accessToken)
-    const user = res.body
+      accessToken = await userLogin(server, user15)
+    })
 
-    expect(user.videoQuota).to.equal(5 * 1024 * 1024)
-  })
+    it('Should have the correct video quota', async function () {
+      const res = await getMyUserInformation(server.url, accessToken)
+      const user = res.body
 
-  it('Should remove me', async function () {
-    {
-      const res = await getUsersList(server.url, server.accessToken)
-      expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined
-    }
+      expect(user.videoQuota).to.equal(5 * 1024 * 1024)
+    })
 
-    await deleteMe(server.url, accessToken)
+    it('Should remove me', async function () {
+      {
+        const res = await getUsersList(server.url, server.accessToken)
+        expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined
+      }
 
-    {
-      const res = await getUsersList(server.url, server.accessToken)
-      expect(res.body.data.find(u => u.username === 'user_15')).to.be.undefined
-    }
+      await deleteMe(server.url, accessToken)
+
+      {
+        const res = await getUsersList(server.url, server.accessToken)
+        expect(res.body.data.find(u => u.username === 'user_15')).to.be.undefined
+      }
+    })
   })
 
-  it('Should block and unblock a user', async function () {
-    const user16 = {
-      username: 'user_16',
-      password: 'my super password'
-    }
-    const resUser = await createUser(server.url, server.accessToken, user16.username, user16.password)
-    const user16Id = resUser.body.user.id
+  describe('User blocking', function () {
+    it('Should block and unblock a user', async function () {
+      const user16 = {
+        username: 'user_16',
+        password: 'my super password'
+      }
+      const resUser = await createUser({
+        url: server.url,
+        accessToken: server.accessToken,
+        username: user16.username,
+        password: user16.password
+      })
+      const user16Id = resUser.body.user.id
 
-    accessToken = await userLogin(server, user16)
+      accessToken = await userLogin(server, user16)
 
-    await getMyUserInformation(server.url, accessToken, 200)
-    await blockUser(server.url, user16Id, server.accessToken)
+      await getMyUserInformation(server.url, accessToken, 200)
+      await blockUser(server.url, user16Id, server.accessToken)
 
-    await getMyUserInformation(server.url, accessToken, 401)
-    await userLogin(server, user16, 400)
+      await getMyUserInformation(server.url, accessToken, 401)
+      await userLogin(server, user16, 400)
 
-    await unblockUser(server.url, user16Id, server.accessToken)
-    accessToken = await userLogin(server, user16)
-    await getMyUserInformation(server.url, accessToken, 200)
+      await unblockUser(server.url, user16Id, server.accessToken)
+      accessToken = await userLogin(server, user16)
+      await getMyUserInformation(server.url, accessToken, 200)
+    })
   })
 
   after(async function () {

+ 1 - 1
server/tests/api/videos/multiple-servers.ts

@@ -164,7 +164,7 @@ describe('Test multiple servers', function () {
         username: 'user1',
         password: 'super_password'
       }
-      await createUser(servers[1].url, servers[1].accessToken, user.username, user.password)
+      await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password })
       const userAccessToken = await userLogin(servers[1], user)
 
       const videoAttributes = {

+ 101 - 14
server/tests/api/videos/video-blacklist.ts

@@ -5,29 +5,31 @@ import { orderBy } from 'lodash'
 import 'mocha'
 import {
   addVideoToBlacklist,
+  createUser,
   flushAndRunMultipleServers,
   getBlacklistedVideosList,
-  getBlacklistedVideosListWithTypeFilter,
   getMyVideos,
-  getSortedBlacklistedVideosList,
   getVideosList,
   killallServers,
   removeVideoFromBlacklist,
+  reRunServer,
   searchVideo,
   ServerInfo,
   setAccessTokensToServers,
   updateVideo,
   updateVideoBlacklist,
   uploadVideo,
-  viewVideo
+  userLogin
 } from '../../../../shared/utils/index'
 import { doubleFollow } from '../../../../shared/utils/server/follows'
 import { waitJobs } from '../../../../shared/utils/server/jobs'
 import { VideoBlacklist, VideoBlacklistType } from '../../../../shared/models/videos'
+import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
+import { UserRole } from '../../../../shared/models/users'
 
 const expect = chai.expect
 
-describe('Test video blacklist management', function () {
+describe('Test video blacklist', function () {
   let servers: ServerInfo[] = []
   let videoId: number
 
@@ -104,7 +106,7 @@ describe('Test video blacklist management', function () {
 
   describe('When listing manually blacklisted videos', function () {
     it('Should display all the blacklisted videos', async function () {
-      const res = await getBlacklistedVideosList(servers[0].url, servers[0].accessToken)
+      const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken })
 
       expect(res.body.total).to.equal(2)
 
@@ -119,7 +121,11 @@ describe('Test video blacklist management', function () {
     })
 
     it('Should display all the blacklisted videos when applying manual type filter', async function () {
-      const res = await getBlacklistedVideosListWithTypeFilter(servers[0].url, servers[0].accessToken, VideoBlacklistType.MANUAL)
+      const res = await getBlacklistedVideosList({
+        url: servers[ 0 ].url,
+        token: servers[ 0 ].accessToken,
+        type: VideoBlacklistType.MANUAL
+      })
 
       expect(res.body.total).to.equal(2)
 
@@ -129,7 +135,11 @@ describe('Test video blacklist management', function () {
     })
 
     it('Should display nothing when applying automatic type filter', async function () {
-      const res = await getBlacklistedVideosListWithTypeFilter(servers[0].url, servers[0].accessToken, VideoBlacklistType.AUTO_BEFORE_PUBLISHED) // tslint:disable:max-line-length
+      const res = await getBlacklistedVideosList({
+        url: servers[ 0 ].url,
+        token: servers[ 0 ].accessToken,
+        type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
+      })
 
       expect(res.body.total).to.equal(0)
 
@@ -139,7 +149,7 @@ describe('Test video blacklist management', function () {
     })
 
     it('Should get the correct sort when sorting by descending id', async function () {
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-id')
+      const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-id' })
       expect(res.body.total).to.equal(2)
 
       const blacklistedVideos = res.body.data
@@ -152,7 +162,7 @@ describe('Test video blacklist management', function () {
     })
 
     it('Should get the correct sort when sorting by descending video name', async function () {
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
+      const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' })
       expect(res.body.total).to.equal(2)
 
       const blacklistedVideos = res.body.data
@@ -165,7 +175,7 @@ describe('Test video blacklist management', function () {
     })
 
     it('Should get the correct sort when sorting by ascending creation date', async function () {
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt')
+      const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: 'createdAt' })
       expect(res.body.total).to.equal(2)
 
       const blacklistedVideos = res.body.data
@@ -182,7 +192,7 @@ describe('Test video blacklist management', function () {
     it('Should change the reason', async function () {
       await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated')
 
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
+      const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' })
       const video = res.body.data.find(b => b.video.id === videoId)
 
       expect(video.reason).to.equal('my super reason updated')
@@ -218,7 +228,7 @@ describe('Test video blacklist management', function () {
 
     it('Should remove a video from the blacklist on server 1', async function () {
       // Get one video in the blacklist
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
+      const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' })
       videoToRemove = res.body.data[0]
       blacklist = res.body.data.slice(1)
 
@@ -239,7 +249,7 @@ describe('Test video blacklist management', function () {
     })
 
     it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () {
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
+      const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' })
       expect(res.body.total).to.equal(1)
 
       const videos = res.body.data
@@ -313,7 +323,7 @@ describe('Test video blacklist management', function () {
     })
 
     it('Should have the correct video blacklist unfederate attribute', async function () {
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt')
+      const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: 'createdAt' })
 
       const blacklistedVideos: VideoBlacklist[] = res.body.data
       const video3Blacklisted = blacklistedVideos.find(b => b.video.uuid === video3UUID)
@@ -338,6 +348,83 @@ describe('Test video blacklist management', function () {
 
   })
 
+  describe('When auto blacklist videos', function () {
+    let userWithoutFlag: string
+    let userWithFlag: string
+
+    before(async function () {
+      this.timeout(20000)
+
+      killallServers([ servers[0] ])
+
+      const config = {
+        'auto_blacklist': {
+          videos: {
+            'of_users': {
+              enabled: true
+            }
+          }
+        }
+      }
+      await reRunServer(servers[0], config)
+
+      {
+        const user = { username: 'user_without_flag', password: 'password' }
+        await createUser({
+          url: servers[ 0 ].url,
+          accessToken: servers[ 0 ].accessToken,
+          username: user.username,
+          adminFlags: UserAdminFlag.NONE,
+          password: user.password,
+          role: UserRole.USER
+        })
+
+        userWithoutFlag = await userLogin(servers[0], user)
+      }
+
+      {
+        const user = { username: 'user_with_flag', password: 'password' }
+        await createUser({
+          url: servers[ 0 ].url,
+          accessToken: servers[ 0 ].accessToken,
+          username: user.username,
+          adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST,
+          password: user.password,
+          role: UserRole.USER
+        })
+
+        userWithFlag = await userLogin(servers[0], user)
+      }
+
+      await waitJobs(servers)
+    })
+
+    it('Should auto blacklist a video', async function () {
+      await uploadVideo(servers[0].url, userWithoutFlag, { name: 'blacklisted' })
+
+      const res = await getBlacklistedVideosList({
+        url: servers[ 0 ].url,
+        token: servers[ 0 ].accessToken,
+        type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
+      })
+
+      expect(res.body.total).to.equal(1)
+      expect(res.body.data[0].video.name).to.equal('blacklisted')
+    })
+
+    it('Should not auto blacklist a video', async function () {
+      await uploadVideo(servers[0].url, userWithFlag, { name: 'not blacklisted' })
+
+      const res = await getBlacklistedVideosList({
+        url: servers[ 0 ].url,
+        token: servers[ 0 ].accessToken,
+        type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
+      })
+
+      expect(res.body.total).to.equal(1)
+    })
+  })
+
   after(async function () {
     killallServers(servers)
   })

+ 28 - 4
server/tests/api/videos/video-change-ownership.ts

@@ -46,8 +46,20 @@ describe('Test video change ownership - nominal', function () {
     await setAccessTokensToServers(servers)
 
     const videoQuota = 42000000
-    await createUser(servers[0].url, servers[0].accessToken, firstUser.username, firstUser.password, videoQuota)
-    await createUser(servers[0].url, servers[0].accessToken, secondUser.username, secondUser.password, videoQuota)
+    await createUser({
+      url: servers[ 0 ].url,
+      accessToken: servers[ 0 ].accessToken,
+      username: firstUser.username,
+      password: firstUser.password,
+      videoQuota: videoQuota
+    })
+    await createUser({
+      url: servers[ 0 ].url,
+      accessToken: servers[ 0 ].accessToken,
+      username: secondUser.username,
+      password: secondUser.password,
+      videoQuota: videoQuota
+    })
 
     firstUserAccessToken = await userLogin(servers[0], firstUser)
     secondUserAccessToken = await userLogin(servers[0], secondUser)
@@ -219,8 +231,20 @@ describe('Test video change ownership - quota too small', function () {
 
     const videoQuota = 42000000
     const limitedVideoQuota = 10
-    await createUser(server.url, server.accessToken, firstUser.username, firstUser.password, videoQuota)
-    await createUser(server.url, server.accessToken, secondUser.username, secondUser.password, limitedVideoQuota)
+    await createUser({
+      url: server.url,
+      accessToken: server.accessToken,
+      username: firstUser.username,
+      password: firstUser.password,
+      videoQuota: videoQuota
+    })
+    await createUser({
+      url: server.url,
+      accessToken: server.accessToken,
+      username: secondUser.username,
+      password: secondUser.password,
+      videoQuota: limitedVideoQuota
+    })
 
     firstUserAccessToken = await userLogin(server, firstUser)
     secondUserAccessToken = await userLogin(server, secondUser)

+ 1 - 1
server/tests/api/videos/video-channels.ts

@@ -270,7 +270,7 @@ describe('Test video channels', function () {
     }
 
     {
-      await createUser(servers[ 0 ].url, servers[ 0 ].accessToken, 'toto', 'password')
+      await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: 'toto', password: 'password' })
       const accessToken = await userLogin(servers[ 0 ], { username: 'toto', password: 'password' })
 
       const res = await getMyUserInformation(servers[ 0 ].url, accessToken)

+ 1 - 1
server/tests/api/videos/video-nsfw.ts

@@ -144,7 +144,7 @@ describe('Test video NSFW policy', function () {
     it('Should create a user having the default nsfw policy', async function () {
       const username = 'user1'
       const password = 'my super password'
-      await createUser(server.url, server.accessToken, username, password)
+      await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password })
 
       userAccessToken = await userLogin(server, { username, password })
 

+ 6 - 1
server/tests/api/videos/video-playlists.ts

@@ -815,7 +815,12 @@ describe('Test video playlists', function () {
     this.timeout(30000)
 
     const user = { username: 'user_1', password: 'password' }
-    const res = await createUser(servers[0].url, servers[0].accessToken, user.username, user.password)
+    const res = await createUser({
+      url: servers[ 0 ].url,
+      accessToken: servers[ 0 ].accessToken,
+      username: user.username,
+      password: user.password
+    })
 
     const userId = res.body.user.id
     const userAccessToken = await userLogin(servers[0], user)

+ 1 - 1
server/tests/api/videos/video-privacy.ts

@@ -78,7 +78,7 @@ describe('Test video privacy', function () {
       username: 'hello',
       password: 'super password'
     }
-    await createUser(servers[0].url, servers[0].accessToken, user.username, user.password)
+    await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password })
 
     const token = await userLogin(servers[0], user)
     await getVideoWithToken(servers[0].url, token, privateVideoUUID, 403)

+ 9 - 7
server/tests/api/videos/videos-filter.ts

@@ -64,13 +64,15 @@ describe('Test videos filter validator', function () {
     for (const server of servers) {
       const moderator = { username: 'moderator', password: 'my super password' }
       await createUser(
-        server.url,
-        server.accessToken,
-        moderator.username,
-        moderator.password,
-        undefined,
-        undefined,
-        UserRole.MODERATOR
+        {
+          url: server.url,
+          accessToken: server.accessToken,
+          username: moderator.username,
+          password: moderator.password,
+          videoQuota: undefined,
+          videoQuotaDaily: undefined,
+          role: UserRole.MODERATOR
+        }
       )
       server['moderatorAccessToken'] = await userLogin(server, moderator)
 

+ 1 - 1
server/tests/api/videos/videos-history.ts

@@ -58,7 +58,7 @@ describe('Test videos history', function () {
       username: 'user_1',
       password: 'super password'
     }
-    await createUser(server.url, server.accessToken, user.username, user.password)
+    await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password })
     userAccessToken = await userLogin(server, user)
   })
 

+ 1 - 1
server/tests/cli/peertube.ts

@@ -24,7 +24,7 @@ describe('Test CLI wrapper', function () {
     server = await runServer(1)
     await setAccessTokensToServers([ server ])
 
-    await createUser(server.url, server.accessToken, 'user_1', 'super password')
+    await createUser({ url: server.url, accessToken: server.accessToken, username: 'user_1', password: 'super password' })
   })
 
   it('Should display no selected instance', async function () {

+ 1 - 1
server/tests/cli/reset-password.ts

@@ -22,7 +22,7 @@ describe('Test reset password scripts', function () {
     server = await runServer(1)
     await setAccessTokensToServers([ server ])
 
-    await createUser(server.url, server.accessToken, 'user_1', 'super password')
+    await createUser({ url: server.url, accessToken: server.accessToken, username: 'user_1', password: 'super password' })
   })
 
   it('Should change the user password from CLI', async function () {

+ 1 - 1
server/tests/cli/update-host.ts

@@ -50,7 +50,7 @@ describe('Test update host scripts', function () {
     await uploadVideo(server.url, server.accessToken, videoAttributes)
 
     // Create a user
-    await createUser(server.url, server.accessToken, 'toto', 'coucou')
+    await createUser({ url: server.url, accessToken: server.accessToken, username: 'toto', password: 'coucou' })
 
     // Create channel
     const videoChannel = {

+ 1 - 1
server/tests/feeds/feeds.ts

@@ -50,7 +50,7 @@ describe('Test syndication feeds', () => {
 
     {
       const attr = { username: 'john', password: 'password' }
-      await createUser(servers[0].url, servers[0].accessToken, attr.username, attr.password)
+      await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: attr.username, password: attr.password })
       userAccessToken = await userLogin(servers[0], attr)
 
       const res = await getMyUserInformation(servers[0].url, userAccessToken)

+ 2 - 2
server/tests/misc-endpoints.ts

@@ -149,8 +149,8 @@ describe('Test misc endpoints', function () {
       await addVideoChannel(server.url, server.accessToken, { name: 'channel1', displayName: 'channel 1' })
       await addVideoChannel(server.url, server.accessToken, { name: 'channel2', displayName: 'channel 2' })
 
-      await createUser(server.url, server.accessToken, 'user1', 'password')
-      await createUser(server.url, server.accessToken, 'user2', 'password')
+      await createUser({ url: server.url, accessToken: server.accessToken, username: 'user1', password: 'password' })
+      await createUser({ url: server.url, accessToken: server.accessToken, username: 'user2', password: 'password' })
 
       const res = await makeGetRequest({
         url: server.url,

+ 1 - 1
server/tests/real-world/populate-database.ts

@@ -78,7 +78,7 @@ function createUserCustom (server: ServerInfo) {
   const username = Date.now().toString() + getRandomInt(0, 100000)
   console.log('Creating user %s.', username)
 
-  return createUser(server.url, server.accessToken, username, 'coucou')
+  return createUser({ url: server.url, accessToken: server.accessToken, username: username, password: 'coucou' })
 }
 
 function uploadCustom (server: ServerInfo) {

+ 2 - 0
shared/models/users/user-create.model.ts

@@ -1,4 +1,5 @@
 import { UserRole } from './user-role'
+import { UserAdminFlag } from './user-flag.model'
 
 export interface UserCreate {
   username: string
@@ -7,4 +8,5 @@ export interface UserCreate {
   videoQuota: number
   videoQuotaDaily: number
   role: UserRole
+  adminFlags?: UserAdminFlag
 }

+ 4 - 0
shared/models/users/user-flag.model.ts

@@ -0,0 +1,4 @@
+export enum UserAdminFlag {
+  NONE = 0,
+  BY_PASS_VIDEO_AUTO_BLACKLIST = 1 << 0
+}

+ 2 - 0
shared/models/users/user-update.model.ts

@@ -1,4 +1,5 @@
 import { UserRole } from './user-role'
+import { UserAdminFlag } from './user-flag.model'
 
 export interface UserUpdate {
   password?: string
@@ -7,4 +8,5 @@ export interface UserUpdate {
   videoQuota?: number
   videoQuotaDaily?: number
   role?: UserRole
+  adminFlags?: UserAdminFlag
 }

+ 5 - 0
shared/models/users/user.model.ts

@@ -3,6 +3,7 @@ import { VideoChannel } from '../videos/channel/video-channel.model'
 import { UserRole } from './user-role'
 import { NSFWPolicyType } from '../videos/nsfw-policy.type'
 import { UserNotificationSetting } from './user-notification-setting.model'
+import { UserAdminFlag } from './user-flag.model'
 
 export interface User {
   id: number
@@ -11,11 +12,15 @@ export interface User {
   emailVerified: boolean
   nsfwPolicy: NSFWPolicyType
 
+  adminFlags?: UserAdminFlag
+
   autoPlayVideo: boolean
   webTorrentEnabled: boolean
   videosHistoryEnabled: boolean
 
   role: UserRole
+  roleLabel: string
+
   videoQuota: number
   videoQuotaDaily: number
   createdAt: Date

+ 25 - 8
shared/utils/users/users.ts

@@ -4,22 +4,37 @@ import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '..
 import { UserRole } from '../../index'
 import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
 import { ServerInfo, userLogin } from '..'
+import { UserAdminFlag } from '../../models/users/user-flag.model'
 
-function createUser (
-  url: string,
+type CreateUserArgs = { url: string,
   accessToken: string,
   username: string,
   password: string,
-  videoQuota = 1000000,
-  videoQuotaDaily = -1,
-  role: UserRole = UserRole.USER,
-  specialStatus = 200
-) {
+  videoQuota?: number,
+  videoQuotaDaily?: number,
+  role?: UserRole,
+  adminFlags?: UserAdminFlag,
+  specialStatus?: number
+}
+function createUser (parameters: CreateUserArgs) {
+  const {
+    url,
+    accessToken,
+    username,
+    adminFlags,
+    password = 'password',
+    videoQuota = 1000000,
+    videoQuotaDaily = -1,
+    role = UserRole.USER,
+    specialStatus = 200
+  } = parameters
+
   const path = '/api/v1/users'
   const body = {
     username,
     password,
     role,
+    adminFlags,
     email: username + '@example.com',
     videoQuota,
     videoQuotaDaily
@@ -35,7 +50,7 @@ function createUser (
 
 async function generateUserAccessToken (server: ServerInfo, username: string) {
   const password = 'my super password'
-  await createUser(server.url, server.accessToken, username, password)
+  await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password })
 
   return userLogin(server, { username, password })
 }
@@ -222,6 +237,7 @@ function updateUser (options: {
   videoQuota?: number,
   videoQuotaDaily?: number,
   password?: string,
+  adminFlags?: UserAdminFlag,
   role?: UserRole
 }) {
   const path = '/api/v1/users/' + options.userId
@@ -233,6 +249,7 @@ function updateUser (options: {
   if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota
   if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend['videoQuotaDaily'] = options.videoQuotaDaily
   if (options.role !== undefined && options.role !== null) toSend['role'] = options.role
+  if (options.adminFlags !== undefined && options.adminFlags !== null) toSend['adminFlags'] = options.adminFlags
 
   return makePutBodyRequest({
     url: options.url,

+ 18 - 33
shared/utils/videos/video-blacklist.ts

@@ -1,4 +1,6 @@
 import * as request from 'supertest'
+import { VideoBlacklistType } from '../../models/videos'
+import { makeGetRequest } from '..'
 
 function addVideoToBlacklist (
   url: string,
@@ -39,40 +41,25 @@ function removeVideoFromBlacklist (url: string, token: string, videoId: number |
           .expect(specialStatus)
 }
 
-function getBlacklistedVideosList (url: string, token: string, specialStatus = 200) {
+function getBlacklistedVideosList (parameters: {
+  url: string,
+  token: string,
+  sort?: string,
+  type?: VideoBlacklistType,
+  specialStatus?: number
+}) {
+  let { url, token, sort, type, specialStatus = 200 } = parameters
   const path = '/api/v1/videos/blacklist/'
 
-  return request(url)
-          .get(path)
-          .query({ sort: 'createdAt' })
-          .set('Accept', 'application/json')
-          .set('Authorization', 'Bearer ' + token)
-          .expect(specialStatus)
-          .expect('Content-Type', /json/)
-}
-
-function getBlacklistedVideosListWithTypeFilter (url: string, token: string, type: number, specialStatus = 200) {
-  const path = '/api/v1/videos/blacklist/'
+  const query = { sort, type }
 
-  return request(url)
-          .get(path)
-          .query({ sort: 'createdAt', type })
-          .set('Accept', 'application/json')
-          .set('Authorization', 'Bearer ' + token)
-          .expect(specialStatus)
-          .expect('Content-Type', /json/)
-}
-
-function getSortedBlacklistedVideosList (url: string, token: string, sort: string, specialStatus = 200) {
-  const path = '/api/v1/videos/blacklist/'
-
-  return request(url)
-          .get(path)
-          .query({ sort: sort })
-          .set('Accept', 'application/json')
-          .set('Authorization', 'Bearer ' + token)
-          .expect(specialStatus)
-          .expect('Content-Type', /json/)
+  return makeGetRequest({
+    url,
+    path,
+    query,
+    token,
+    statusCodeExpected: specialStatus
+  })
 }
 
 // ---------------------------------------------------------------------------
@@ -81,7 +68,5 @@ export {
   addVideoToBlacklist,
   removeVideoFromBlacklist,
   getBlacklistedVideosList,
-  getBlacklistedVideosListWithTypeFilter,
-  getSortedBlacklistedVideosList,
   updateVideoBlacklist
 }