User:X!/URS
This is a planning and information page for Unblock Request System (URS).
Mission
[edit]The mission of URS is to provide an organized, intuitive interface for blocked users to discuss terms of their unblock in private with trusted administrators.
Users
[edit]There are 4 types of users of URS. These are not mutually exclusive, so one user can be classified in two or more types of users.
- Blocked users - users who are requesting an unblock. They want to access the frontend for requesting and negotiating their unblock, as well as 1 part of the api for an RSS feed.
- Wikipedia administrators - administrators on Wikipedia who negotiate and unblock users. They want to access the frontend for discussing and closing requests, as well as preferences for their user account on the tool.
- Tool administrators - Users who have higher rights on the tool than the Wikipedia administrators. They have access to the backend, used for administrating users, approving admins, and other maintenance tasks.
- IRC Bot - A bot that hangs out in the unblock channel on IRC that would give status reports when asked, or at a set interval. Interacts with the api application.
Applications
[edit]There are 3 types of applications in URS, each one aimed at a different user.
- API - The API is similar to the MediaWiki API (in fact, it uses the same code) in that it returns results in XML, JSON, PHP, YAML, etc formats that are generated by the backend. There are 3 possible uses for it, 2 are aimed at the IRC Bot, and 1 is at the blocked users.
- Backend - The backend is where the tool admins can administrate the workings of the tool. They can edit and delete requests, approve and block wiki admins, and edit the templates that users can use.
- Frontend - This is where the meat is. It is used by 2 users: the blocked users and the wiki admins. They blocked editors can post requests here, check the status, and post replies. The admins can view a list of requests, edit requests, reply, close requests, ban blocked editors, and change their account preferences.
Stories
[edit]Each application has several dedicated purposes. This describes all the features of each application.
API
[edit]Story A1: Get current requests
[edit]This module is secured with a username and password, which the bot must use to get the private list. The list provides a list of open requests, with information such as the IP, number of replies, etc. The bot can also refine this query by providing a limit of entries, by filtering IP, username, etc.
Story A2: Get a single request
[edit]This module provides similar info to A1, except that it only lists 1 certain entry, and it has the content of all the replies included with the info about the request. It is also secured with a username and password.
Story A3: RSS feed
[edit]Unlike this other stories, this is meant to be accessed by the blocked user with an RSS reader. It is not protected by a username and password, but rather by a token that they are given when they make the request. It is a feed of all the comments made on the request, and nothing else
Backend
[edit]All of these stories are meant to be accessed only with a username and password, so it is not mentioned individually in each description.
Story B1: An admin can edit and delete requests
[edit]The admin views a list of requests, and can both edit and delete them as needed.
Story B2: An admin can approve users
[edit]The admin views a list of users, which can be filtered to view only unapproved users. They then can approve the user to edit requests. Until then, the user cannot read requests or edit them until they are approved.
The approval process goes like this:
- User applies for an account
- User makes post on talk page confirming
- User verifies email address
- Admin approved once steps 2 and 3 are completed
Story B3: An admin can block abusive users
[edit]If necessary, to prevent abuse, an admin can block a user. They can provide a reason for the block, as well as an expiry.
Frontend (blocked users)
[edit]Story FB1: Users can post a request
[edit]The user fills out a form with username or IP address, reason for unblock, email, etc. If a username is not given, it will use the users current IP address. If no block is found, on the username, IP, or an autoblock, it will reject the request. If no reason is given, it will reject the request. If the username/IP/email is blocked, it will reject the request.
Once the request is made, the user verifies their email with a confirm page (Story FB2), and it is then pushed live.
Story FB2: Users can confirm their email address
[edit]This is accessed only with a link in an email message. If the token provided matches the database, it approves the request and pushes it live out of the holding area. An email is then sent with a link to the request page (Story FB3), with a secret token that they can access it with.
Story FB3: User can check the status of their request
[edit]This area is protected by a token they the user is provided after verifying their email address. If they provide the right token, their request is shown. It shows all the block info, the username, and a list of public replies. They can see the admin that made each reply, timestamp, and content of the reply. The content is passed through the en.wikipedia parser, so it can use templates stored on enwiki, images hosted on commons, and all the wikimarkup on Wikipedia.
Story FB4: User can reply to their request
[edit]If a concern is made, they can make another comment to the request, just as admins are able to do.
Frontend (wiki admins)
[edit]This is all protected with a username and password, so it is not worth mentioning in each story.
Story FA1: An admin can view a list of requests
[edit]The admin can view all the currently open requests, which can be filtered by IP, date, number of replies, block type, status of request, etc. The list is paginated, so they can view up to ## request at a time before going to the next page.
Story FA2: An admin can view a request
[edit]Similar to story FB3, all the blockinfo is shown, the username, and the parsed replies.
Story FA3: The admin can reply to the request
[edit]There are two types of replies: public, and private. Public replies can be viewed by both the user and the admins, while private replies can only be viewed by the admins. It has a textbox for the content of the reply, as well as a dropdown for changing the status of the request (new, on hold, denied, accepted). This also provides for the ability to close a request, by choosing "denied" or "accepted". This does not remove the request from the main page, so other admins can review it. After 30 days, it is removed and archived automatically.
Story FA4: An admin can ban an IP, username, block ID, or email address
[edit]The admin can block a user, etc by visiting this module. They enter the user to be blocked, a time of expiry, and a reason.
Story FA5: sfDoctrineApplyPlugin pages
[edit]To handle the user administration, URS will be using the sfDoctrineApplyPlugin. This provides account application, email confirmation, user settings, etc. The details are not too important, because it's all in the README.
Database
[edit]YAML
[edit]UrsRequest:
actAs: { Timestampable: ~ }
columns:
name:
type: string(255)
notnull: true
ip:
type: string(255)
notnull: true
wiki_block:
type: integer
email:
type: string(255)
notnull: true
email_token:
type: string(32)
type:
type: integer(1)
notnull: true
default: 0
status:
type: integer(1)
notnull: true
default: 0
token:
type: string(32)
notnull: true
unique: true
is_activated:
type: boolean
notnull: true
default: 0
expires_at:
type: timestamp
notnull: true
UrsComment:
actAs: { Timestampable: ~ }
columns:
request_id:
type: integer
notnull: true
profile_id:
type: string(255)
notnull: true
text:
type: blob
notnull: true
status:
type: integer(1)
notnull: true
default: 0
relations:
UrsRequest:
local: request_id
foreign: id
foreignAlias: Comments
sfGuardUserProfile:
local: profile_id
foreign: id
foreignAlias: Comments
UrsBlock:
actAs: { Timestampable: ~ }
columns:
type:
type: integer
notnull: true
target:
type: string(255)
notnull: true
reason:
type: string(255)
notnull: true
expiry:
type: timestamp
notnull: true
is_active:
type: boolean
notnull: true
default: 1
sfGuardUserProfile:
tableName: sf_guard_user_profile
columns:
id:
type: integer
primary: true
autoincrement: true
wiki_id:
type: integer
notnull: true
user_id:
type: integer
notnull: true
email:
type: string(255)
fullname:
type: string(255)
validate:
type: string(17)
is_blocked:
type: boolean
notnull: true
default: 0
is_approved:
type: boolean
notnull: true
default: 0
relations:
User:
class: sfGuardUser
foreign: id
local: user_id
type: one
onDelete: cascade
foreignType: one
foreignAlias: Profile
sfGuardGroup:
actAs: [Timestampable]
columns:
name:
type: string(255)
unique: true
description: string(1000)
relations:
Users:
class: sfGuardUser
refClass: sfGuardUserGroup
local: group_id
foreign: user_id
foreignAlias: Groups
Permissions:
class: sfGuardPermission
local: group_id
foreign: permission_id
refClass: sfGuardGroupPermission
foreignAlias: Groups
sfGuardPermission:
actAs: [Timestampable]
columns:
name:
type: string(255)
unique: true
description: string(1000)
sfGuardGroupPermission:
options:
symfony:
form: false
filter: false
actAs: [Timestampable]
columns:
group_id:
type: integer
primary: true
permission_id:
type: integer
primary: true
relations:
Group:
class: sfGuardGroup
local: group_id
onDelete: CASCADE
Permission:
class: sfGuardPermission
local: permission_id
onDelete: CASCADE
sfGuardUser:
actAs: [Timestampable]
columns:
first_name: string(255)
last_name: string(255)
email_address:
type: string(255)
notnull: true
unique: true
username:
type: string(128)
notnull: true
unique: true
algorithm:
type: string(128)
default: sha1
notnull: true
salt: string(128)
password: string(128)
is_active:
type: boolean
default: 1
is_super_admin:
type: boolean
default: false
last_login:
type: timestamp
indexes:
is_active_idx:
fields: [is_active]
relations:
Groups:
class: sfGuardGroup
local: user_id
foreign: group_id
refClass: sfGuardUserGroup
foreignAlias: Users
Permissions:
class: sfGuardPermission
local: user_id
foreign: permission_id
refClass: sfGuardUserPermission
foreignAlias: Users
sfGuardUserPermission:
options:
symfony:
form: false
filter: false
actAs: [Timestampable]
columns:
user_id:
type: integer
primary: true
permission_id:
type: integer
primary: true
relations:
User:
class: sfGuardUser
local: user_id
onDelete: CASCADE
Permission:
class: sfGuardPermission
local: permission_id
onDelete: CASCADE
sfGuardUserGroup:
options:
symfony:
form: false
filter: false
actAs: [Timestampable]
columns:
user_id:
type: integer
primary: true
group_id:
type: integer
primary: true
relations:
User:
class: sfGuardUser
local: user_id
onDelete: CASCADE
Group:
class: sfGuardGroup
local: group_id
onDelete: CASCADE
sfGuardRememberKey:
options:
symfony:
form: false
filter: false
actAs: [Timestampable]
columns:
user_id: integer
remember_key: string(32)
ip_address: string(50)
relations:
User:
class: sfGuardUser
local: user_id
foreignAlias: RememberKeys
foreignType: one
onDelete: CASCADE
sfGuardForgotPassword:
options:
symfony:
form: false
filter: false
actAs: [Timestampable]
columns:
user_id:
type: integer
notnull: true
unique_key: string(255)
expires_at:
type: timestamp
notnull: true
relations:
User:
class: sfGuardUser
local: user_id
type: one
foreignType: one
foreignAlias: ForgotPassword
onDelete: CASCADE
SQL
[edit]CREATE TABLE urs_block (id BIGINT AUTO_INCREMENT, type BIGINT NOT NULL, target VARCHAR(255) NOT NULL, reason VARCHAR(255) NOT NULL, expiry DATETIME NOT NULL, is_active TINYINT(1) DEFAULT '1' NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY(id)) ENGINE = INNODB;
CREATE TABLE urs_comment (id BIGINT AUTO_INCREMENT, request_id BIGINT NOT NULL, profile_id VARCHAR(255) NOT NULL, text LONGBLOB NOT NULL, status TINYINT DEFAULT 0 NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, INDEX request_id_idx (request_id), INDEX profile_id_idx (profile_id), PRIMARY KEY(id)) ENGINE = INNODB;
CREATE TABLE urs_request (id BIGINT AUTO_INCREMENT, name VARCHAR(255) NOT NULL, ip VARCHAR(255) NOT NULL, wiki_block BIGINT, email VARCHAR(255) NOT NULL, email_token VARCHAR(32), type TINYINT DEFAULT 0 NOT NULL, status TINYINT DEFAULT 0 NOT NULL, token VARCHAR(32) NOT NULL UNIQUE, is_activated TINYINT(1) DEFAULT '0' NOT NULL, expires_at DATETIME NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY(id)) ENGINE = INNODB;
CREATE TABLE sf_guard_forgot_password (id BIGINT AUTO_INCREMENT, user_id BIGINT NOT NULL, unique_key VARCHAR(255), expires_at DATETIME NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, INDEX user_id_idx (user_id), PRIMARY KEY(id)) ENGINE = INNODB;
CREATE TABLE sf_guard_group (id BIGINT AUTO_INCREMENT, name VARCHAR(255) UNIQUE, description TEXT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY(id)) ENGINE = INNODB;
CREATE TABLE sf_guard_group_permission (group_id BIGINT, permission_id BIGINT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY(group_id, permission_id)) ENGINE = INNODB;
CREATE TABLE sf_guard_permission (id BIGINT AUTO_INCREMENT, name VARCHAR(255) UNIQUE, description TEXT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY(id)) ENGINE = INNODB;
CREATE TABLE sf_guard_remember_key (id BIGINT AUTO_INCREMENT, user_id BIGINT, remember_key VARCHAR(32), ip_address VARCHAR(50), created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, INDEX user_id_idx (user_id), PRIMARY KEY(id)) ENGINE = INNODB;
CREATE TABLE sf_guard_user (id BIGINT AUTO_INCREMENT, first_name VARCHAR(255), last_name VARCHAR(255), email_address VARCHAR(255) NOT NULL UNIQUE, username VARCHAR(128) NOT NULL UNIQUE, algorithm VARCHAR(128) DEFAULT 'sha1' NOT NULL, salt VARCHAR(128), password VARCHAR(128), is_active TINYINT(1) DEFAULT '1', is_super_admin TINYINT(1) DEFAULT '0', last_login DATETIME, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, INDEX is_active_idx_idx (is_active), PRIMARY KEY(id)) ENGINE = INNODB;
CREATE TABLE sf_guard_user_group (user_id BIGINT, group_id BIGINT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY(user_id, group_id)) ENGINE = INNODB;
CREATE TABLE sf_guard_user_permission (user_id BIGINT, permission_id BIGINT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY(user_id, permission_id)) ENGINE = INNODB;
CREATE TABLE sf_guard_user_profile (id BIGINT AUTO_INCREMENT, wiki_id BIGINT NOT NULL, user_id BIGINT NOT NULL, email VARCHAR(255), fullname VARCHAR(255), validate VARCHAR(17), is_blocked TINYINT(1) DEFAULT '0' NOT NULL, is_approved TINYINT(1) DEFAULT '0' NOT NULL, INDEX user_id_idx (user_id), PRIMARY KEY(id)) ENGINE = INNODB;
ALTER TABLE urs_comment ADD CONSTRAINT urs_comment_request_id_urs_request_id FOREIGN KEY (request_id) REFERENCES urs_request(id);
ALTER TABLE urs_comment ADD CONSTRAINT urs_comment_profile_id_sf_guard_user_profile_id FOREIGN KEY (profile_id) REFERENCES sf_guard_user_profile(id);
ALTER TABLE sf_guard_forgot_password ADD CONSTRAINT sf_guard_forgot_password_user_id_sf_guard_user_id FOREIGN KEY (user_id) REFERENCES sf_guard_user(id) ON DELETE CASCADE;
ALTER TABLE sf_guard_group_permission ADD CONSTRAINT sf_guard_group_permission_permission_id_sf_guard_permission_id FOREIGN KEY (permission_id) REFERENCES sf_guard_permission(id) ON DELETE CASCADE;
ALTER TABLE sf_guard_group_permission ADD CONSTRAINT sf_guard_group_permission_group_id_sf_guard_group_id FOREIGN KEY (group_id) REFERENCES sf_guard_group(id) ON DELETE CASCADE;
ALTER TABLE sf_guard_remember_key ADD CONSTRAINT sf_guard_remember_key_user_id_sf_guard_user_id FOREIGN KEY (user_id) REFERENCES sf_guard_user(id) ON DELETE CASCADE;
ALTER TABLE sf_guard_user_group ADD CONSTRAINT sf_guard_user_group_user_id_sf_guard_user_id FOREIGN KEY (user_id) REFERENCES sf_guard_user(id) ON DELETE CASCADE;
ALTER TABLE sf_guard_user_group ADD CONSTRAINT sf_guard_user_group_group_id_sf_guard_group_id FOREIGN KEY (group_id) REFERENCES sf_guard_group(id) ON DELETE CASCADE;
ALTER TABLE sf_guard_user_permission ADD CONSTRAINT sf_guard_user_permission_user_id_sf_guard_user_id FOREIGN KEY (user_id) REFERENCES sf_guard_user(id) ON DELETE CASCADE;
ALTER TABLE sf_guard_user_permission ADD CONSTRAINT sf_guard_user_permission_permission_id_sf_guard_permission_id FOREIGN KEY (permission_id) REFERENCES sf_guard_permission(id) ON DELETE CASCADE;
ALTER TABLE sf_guard_user_profile ADD CONSTRAINT sf_guard_user_profile_user_id_sf_guard_user_id FOREIGN KEY (user_id) REFERENCES sf_guard_user(id) ON DELETE CASCADE;