Add Keep email private (see issue #571). (#571)

- Add site-wide option DEFAULT_KEEP_EMAIL_PRIVATE.
- Add the new option to the install and admin/config pages.
- Add the new option to app.ini in the service section.
- Add the new option to the settings struct.
- Add English text strings to i18n.
- Add field KeepEmailPrivate to user struct.
- Add field KeepEmailPrivate to user form.
- Add option to UI.
- Add using noreply email address if user has "Keep Email Private".
An email address <LowerName>@<NO_REPLY_ADDRESS> is now used in commit
messages (and hopefully all other git log relevant places). The
change relies on the fact that git commands should use
user.NetGitSig().
- Add hiding of email address in UI, if user has set "Keep Email Private".
- Add condition to show email address only on explore/users and user
pages, if user has not set "Keep Email Private".
- Add noreply email in API if set "Keep Email Private".
- Add a new service setting NO_REPLY_ADDRESS. The value of this
setting is used as the domain part for the user's email address in
git log, iff he decides to keep his email address private.
If the user decides to keep his email address private and this
option is not set 'noreply.example.org' is used, which no MTA
should send email to.

Add NO_REPLY_ADDRESS to conf/app.ini.
This commit is contained in:
derSuessmann 2017-01-08 04:12:03 +01:00 committed by Lunny Xiao
parent 6072b03291
commit 51d578ff33
12 changed files with 88 additions and 28 deletions

7
conf/app.ini vendored
View file

@ -190,6 +190,13 @@ ENABLE_REVERSE_PROXY_AUTHENTICATION = false
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
; Enable captcha validation for registration ; Enable captcha validation for registration
ENABLE_CAPTCHA = true ENABLE_CAPTCHA = true
; Default value for KeepEmailPrivate
; New user will get the value of this setting copied into their profile
DEFAULT_KEEP_EMAIL_PRIVATE = false
; Default value for the domain part of the user's email address in the git log
; if he has set KeepEmailPrivate true. The user's email replaced with a
; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS.
NO_REPLY_ADDRESS = noreply.example.org
[webhook] [webhook]
; Hook task queue length, increase if webhook shooting starts hanging ; Hook task queue length, increase if webhook shooting starts hanging

View file

@ -75,19 +75,20 @@ type User struct {
Name string `xorm:"UNIQUE NOT NULL"` Name string `xorm:"UNIQUE NOT NULL"`
FullName string FullName string
// Email is the primary email address (to be used for communication) // Email is the primary email address (to be used for communication)
Email string `xorm:"NOT NULL"` Email string `xorm:"NOT NULL"`
Passwd string `xorm:"NOT NULL"` KeepEmailPrivate bool
LoginType LoginType Passwd string `xorm:"NOT NULL"`
LoginSource int64 `xorm:"NOT NULL DEFAULT 0"` LoginType LoginType
LoginName string LoginSource int64 `xorm:"NOT NULL DEFAULT 0"`
Type UserType LoginName string
OwnedOrgs []*User `xorm:"-"` Type UserType
Orgs []*User `xorm:"-"` OwnedOrgs []*User `xorm:"-"`
Repos []*Repository `xorm:"-"` Orgs []*User `xorm:"-"`
Location string Repos []*Repository `xorm:"-"`
Website string Location string
Rands string `xorm:"VARCHAR(10)"` Website string
Salt string `xorm:"VARCHAR(10)"` Rands string `xorm:"VARCHAR(10)"`
Salt string `xorm:"VARCHAR(10)"`
Created time.Time `xorm:"-"` Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"INDEX"` CreatedUnix int64 `xorm:"INDEX"`
@ -170,13 +171,22 @@ func (u *User) AfterSet(colName string, _ xorm.Cell) {
} }
} }
// getEmail returns an noreply email, if the user has set to keep his
// email address private, otherwise the primary email address.
func (u *User) getEmail() string {
if u.KeepEmailPrivate {
return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress)
}
return u.Email
}
// APIFormat converts a User to api.User // APIFormat converts a User to api.User
func (u *User) APIFormat() *api.User { func (u *User) APIFormat() *api.User {
return &api.User{ return &api.User{
ID: u.ID, ID: u.ID,
UserName: u.Name, UserName: u.Name,
FullName: u.FullName, FullName: u.FullName,
Email: u.Email, Email: u.getEmail(),
AvatarURL: u.AvatarLink(), AvatarURL: u.AvatarLink(),
} }
} }
@ -361,7 +371,7 @@ func (u *User) GetFollowing(page int) ([]*User, error) {
func (u *User) NewGitSig() *git.Signature { func (u *User) NewGitSig() *git.Signature {
return &git.Signature{ return &git.Signature{
Name: u.DisplayName(), Name: u.DisplayName(),
Email: u.Email, Email: u.getEmail(),
When: time.Now(), When: time.Now(),
} }
} }
@ -616,6 +626,8 @@ func CreateUser(u *User) (err error) {
return ErrEmailAlreadyUsed{u.Email} return ErrEmailAlreadyUsed{u.Email}
} }
u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
u.LowerName = strings.ToLower(u.Name) u.LowerName = strings.ToLower(u.Name)
u.AvatarEmail = u.Email u.AvatarEmail = u.Email
u.Avatar = base.HashEmail(u.AvatarEmail) u.Avatar = base.HashEmail(u.AvatarEmail)

View file

@ -38,12 +38,14 @@ type InstallForm struct {
RegisterConfirm bool RegisterConfirm bool
MailNotify bool MailNotify bool
OfflineMode bool OfflineMode bool
DisableGravatar bool DisableGravatar bool
EnableFederatedAvatar bool EnableFederatedAvatar bool
DisableRegistration bool DisableRegistration bool
EnableCaptcha bool EnableCaptcha bool
RequireSignInView bool RequireSignInView bool
DefaultKeepEmailPrivate bool
NoReplyAddress string
AdminName string `binding:"OmitEmpty;AlphaDashDot;MaxSize(30)" locale:"install.admin_name"` AdminName string `binding:"OmitEmpty;AlphaDashDot;MaxSize(30)" locale:"install.admin_name"`
AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"` AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"`
@ -97,11 +99,12 @@ func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding
// UpdateProfileForm form for updating profile // UpdateProfileForm form for updating profile
type UpdateProfileForm struct { type UpdateProfileForm struct {
Name string `binding:"OmitEmpty;MaxSize(35)"` Name string `binding:"OmitEmpty;MaxSize(35)"`
FullName string `binding:"MaxSize(100)"` FullName string `binding:"MaxSize(100)"`
Email string `binding:"Required;Email;MaxSize(254)"` Email string `binding:"Required;Email;MaxSize(254)"`
Website string `binding:"Url;MaxSize(100)"` KeepEmailPrivate bool
Location string `binding:"MaxSize(50)"` Website string `binding:"Url;MaxSize(100)"`
Location string `binding:"MaxSize(50)"`
} }
// Validate valideates the fields // Validate valideates the fields

View file

@ -838,6 +838,8 @@ var Service struct {
EnableReverseProxyAuth bool EnableReverseProxyAuth bool
EnableReverseProxyAutoRegister bool EnableReverseProxyAutoRegister bool
EnableCaptcha bool EnableCaptcha bool
DefaultKeepEmailPrivate bool
NoReplyAddress string
} }
func newService() { func newService() {
@ -850,6 +852,8 @@ func newService() {
Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool() Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool()
Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool() Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()
Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool() Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool()
Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org")
} }
var logLevels = map[string]string{ var logLevels = map[string]string{

View file

@ -124,6 +124,10 @@ save_config_failed = Fail to save configuration: %v
invalid_admin_setting = Admin account setting is invalid: %v invalid_admin_setting = Admin account setting is invalid: %v
install_success = Welcome! We're glad that you chose Gitea, have fun and take care. install_success = Welcome! We're glad that you chose Gitea, have fun and take care.
invalid_log_root_path = Log root path is invalid: %v invalid_log_root_path = Log root path is invalid: %v
default_keep_email_private = Default Value for Keep Email Private
default_keep_email_private_popup = This is the default value for the visibility of the user's email address. If set to true the email address of all new users will be hidden until the user changes his setting.
no_reply_address = No-reply Address
no_reply_address_helper = Domain for the user's email address in git logs if he keeps his email address private. E.g. user 'joe' and 'noreply.example.org' will be 'joe@noreply.example.org'
[home] [home]
uname_holder = Username or email uname_holder = Username or email
@ -307,6 +311,8 @@ add_new_email = Add new email address
add_email = Add email add_email = Add email
add_email_confirmation_sent = A new confirmation email has been sent to '%s', please check your inbox within the next %d hours to complete the confirmation process. add_email_confirmation_sent = A new confirmation email has been sent to '%s', please check your inbox within the next %d hours to complete the confirmation process.
add_email_success = Your new email address was successfully added. add_email_success = Your new email address was successfully added.
keep_email_private = Keep Email Address Private
keep_email_private_popup = Your email address will be hidden from other users if this option is set.
manage_ssh_keys = Manage SSH Keys manage_ssh_keys = Manage SSH Keys
add_key = Add Key add_key = Add Key
@ -1112,6 +1118,8 @@ config.disable_key_size_check = Disable Minimum Key Size Check
config.enable_captcha = Enable Captcha config.enable_captcha = Enable Captcha
config.active_code_lives = Active Code Lives config.active_code_lives = Active Code Lives
config.reset_password_code_lives = Reset Password Code Lives config.reset_password_code_lives = Reset Password Code Lives
config.default_keep_email_private = Default Value for Keep Email Private
config.no_reply_address = No-reply Address
config.webhook_config = Webhook Configuration config.webhook_config = Webhook Configuration
config.queue_length = Queue Length config.queue_length = Queue Length

View file

@ -111,6 +111,8 @@ func Install(ctx *context.Context) {
form.DisableRegistration = setting.Service.DisableRegistration form.DisableRegistration = setting.Service.DisableRegistration
form.EnableCaptcha = setting.Service.EnableCaptcha form.EnableCaptcha = setting.Service.EnableCaptcha
form.RequireSignInView = setting.Service.RequireSignInView form.RequireSignInView = setting.Service.RequireSignInView
form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
form.NoReplyAddress = setting.Service.NoReplyAddress
auth.AssignForm(form, ctx.Data) auth.AssignForm(form, ctx.Data)
ctx.HTML(200, tplInstall) ctx.HTML(200, tplInstall)
@ -291,6 +293,8 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(com.ToStr(form.DisableRegistration)) cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(com.ToStr(form.DisableRegistration))
cfg.Section("service").Key("ENABLE_CAPTCHA").SetValue(com.ToStr(form.EnableCaptcha)) cfg.Section("service").Key("ENABLE_CAPTCHA").SetValue(com.ToStr(form.EnableCaptcha))
cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(com.ToStr(form.RequireSignInView)) cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(com.ToStr(form.RequireSignInView))
cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(com.ToStr(form.DefaultKeepEmailPrivate))
cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(com.ToStr(form.NoReplyAddress))
cfg.Section("").Key("RUN_MODE").SetValue("prod") cfg.Section("").Key("RUN_MODE").SetValue("prod")

View file

@ -91,6 +91,7 @@ func SettingsPost(ctx *context.Context, form auth.UpdateProfileForm) {
ctx.User.FullName = form.FullName ctx.User.FullName = form.FullName
ctx.User.Email = form.Email ctx.User.Email = form.Email
ctx.User.KeepEmailPrivate = form.KeepEmailPrivate
ctx.User.Website = form.Website ctx.User.Website = form.Website
ctx.User.Location = form.Location ctx.User.Location = form.Location
if err := models.UpdateUser(ctx.User); err != nil { if err := models.UpdateUser(ctx.User); err != nil {

View file

@ -119,6 +119,10 @@
<dd><i class="fa fa{{if .Service.DisableMinimumKeySizeCheck}}-check{{end}}-square-o"></i></dd>*/}} <dd><i class="fa fa{{if .Service.DisableMinimumKeySizeCheck}}-check{{end}}-square-o"></i></dd>*/}}
<dt>{{.i18n.Tr "admin.config.enable_captcha"}}</dt> <dt>{{.i18n.Tr "admin.config.enable_captcha"}}</dt>
<dd><i class="fa fa{{if .Service.EnableCaptcha}}-check{{end}}-square-o"></i></dd> <dd><i class="fa fa{{if .Service.EnableCaptcha}}-check{{end}}-square-o"></i></dd>
<dt>{{.i18n.Tr "admin.config.default_keep_email_private"}}</dt>
<dd><i class="fa fa{{if .Service.DefaultKeepEmailPrivate}}-check{{end}}-square-o"></i></dd>
<dt>{{.i18n.Tr "admin.config.no_reply_address"}}</dt>
<dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd>
<div class="ui divider"></div> <div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.active_code_lives"}}</dt> <dt>{{.i18n.Tr "admin.config.active_code_lives"}}</dt>
<dd>{{.Service.ActiveCodeLives}} {{.i18n.Tr "tool.raw_minutes"}}</dd> <dd>{{.Service.ActiveCodeLives}} {{.i18n.Tr "tool.raw_minutes"}}</dd>

View file

@ -16,7 +16,7 @@
{{if .Location}} {{if .Location}}
<i class="octicon octicon-location"></i> {{.Location}} <i class="octicon octicon-location"></i> {{.Location}}
{{end}} {{end}}
{{if and $.ShowUserEmail .Email $.IsSigned}} {{if and $.ShowUserEmail .Email $.IsSigned (not .KeepEmailPrivate)}}
<i class="octicon octicon-mail"></i> <i class="octicon octicon-mail"></i>
<a href="mailto:{{.Email}}" rel="nofollow">{{.Email}}</a> <a href="mailto:{{.Email}}" rel="nofollow">{{.Email}}</a>
{{end}} {{end}}

View file

@ -206,6 +206,17 @@
<input name="require_sign_in_view" type="checkbox" {{if .require_sign_in_view}}checked{{end}}> <input name="require_sign_in_view" type="checkbox" {{if .require_sign_in_view}}checked{{end}}>
</div> </div>
</div> </div>
<div class="inline field">
<div class="ui checkbox">
<label class="poping up" data-content="{{.i18n.Tr "install.default_keep_email_private_popup"}}"><strong>{{.i18n.Tr "install.default_keep_email_private"}}</strong></label>
<input name="default_keep_email_private" type="checkbox" {{if .default_keep_email_private}}checked{{end}}>
</div>
</div>
<div class="inline field">
<label for="no_reply_address">{{.i18n.Tr "install.no_reply_address"}}</label>
<input id="_no_reply_address" name="no_reply_address" value="{{.no_reply_address}}">
<span class="help">{{.i18n.Tr "install.no_reply_address_helper"}}</span>
</div>
</div> </div>
</div> </div>

View file

@ -22,7 +22,7 @@
{{if .Owner.Location}} {{if .Owner.Location}}
<li><i class="octicon octicon-location"></i> {{.Owner.Location}}</li> <li><i class="octicon octicon-location"></i> {{.Owner.Location}}</li>
{{end}} {{end}}
{{if and $.ShowUserEmail .Owner.Email .IsSigned}} {{if or (and $.ShowUserEmail .Owner.Email .IsSigned) (and .Owner.Email .IsSigned (not .Owner.KeepEmailPrivate))}}
<li> <li>
<i class="octicon octicon-mail"></i> <i class="octicon octicon-mail"></i>
<a href="mailto:{{.Owner.Email}}" rel="nofollow">{{.Owner.Email}}</a> <a href="mailto:{{.Owner.Email}}" rel="nofollow">{{.Owner.Email}}</a>

View file

@ -27,6 +27,12 @@
<label for="email">{{.i18n.Tr "email"}}</label> <label for="email">{{.i18n.Tr "email"}}</label>
<input id="email" name="email" value="{{.SignedUser.Email}}"> <input id="email" name="email" value="{{.SignedUser.Email}}">
</div> </div>
<div class="inline field">
<div class="ui checkbox" id="keep-email-private">
<label class="poping up" data-content="{{.i18n.Tr "settings.keep_email_private_popup"}}"><strong>{{.i18n.Tr "settings.keep_email_private"}}</strong></label>
<input name="keep_email_private" type="checkbox" {{if .SignedUser.KeepEmailPrivate}}checked{{end}}>
</div>
</div>
<div class="field {{if .Err_Website}}error{{end}}"> <div class="field {{if .Err_Website}}error{{end}}">
<label for="website">{{.i18n.Tr "settings.website"}}</label> <label for="website">{{.i18n.Tr "settings.website"}}</label>
<input id="website" name="website" type="url" value="{{.SignedUser.Website}}"> <input id="website" name="website" type="url" value="{{.SignedUser.Website}}">