Merge branch 'master' into openid
authorTom Hughes <tom@compton.nu>
Fri, 10 Jun 2011 18:11:08 +0000 (19:11 +0100)
committerTom Hughes <tom@compton.nu>
Fri, 10 Jun 2011 18:11:08 +0000 (19:11 +0100)
1  2 
app/controllers/user_controller.rb
app/models/user.rb
app/views/user/login.html.erb
config/locales/en.yml

index 4128b61d4a0982cf43ba1b79e7bfe45db3c97d05,283e11936f55b2b5ec682f7c672e6f863966fcec..9e2961df2716fa95696d1efabf92cf5ec57c04ea
@@@ -27,53 -27,22 +27,53 @@@ class UserController < ApplicationContr
        render :update do |page|
          page.replace_html "contributorTerms", :partial => "terms"
        end
 +    elsif using_open_id?
 +      # The redirect from the OpenID provider reenters here
 +      # again and we need to pass the parameters through to
 +      # the open_id_authentication function
 +      @user = session.delete(:new_user)
 +
 +      openid_verify(nil, @user) do |user|
 +      end
 +
 +      if @user.openid_url.nil? or @user.invalid?
 +        render :action => 'new'
 +      else
 +        render :action => 'terms'
 +      end
      else
 +      session[:referer] = params[:referer]
 +
        @title = t 'user.terms.title'
        @user = User.new(params[:user]) if params[:user]
  
 +      if params[:user] and params[:user][:openid_url] and @user.pass_crypt.empty?
 +        # We are creating an account with OpenID and no password
 +        # was specified so create a random one
 +        @user.pass_crypt = ActiveSupport::SecureRandom.base64(16) 
 +        @user.pass_crypt_confirmation = @user.pass_crypt 
 +      end
 +
        if @user
          if @user.invalid?
            if @user.new_record?
 +            # Something is wrong with a new user, so rerender the form
              render :action => :new
            else
 +            # Error in existing user, so go to account settings
              flash[:errors] = @user.errors
              redirect_to :action => :account, :display_name => @user.display_name
            end
          elsif @user.terms_agreed?
 +          # Already agreed to terms, so just show settings
            redirect_to :action => :account, :display_name => @user.display_name
 +        elsif params[:user] and params[:user][:openid_url]
 +          # Verify OpenID before moving on
 +          session[:new_user] = @user
 +          openid_verify(params[:user][:openid_url], @user)
          end
        else
 +        # Not logged in, so redirect to the login page
          redirect_to :action => :login, :referer => request.request_uri
        end
      end
        
        if @user.save
          flash[:notice] = t 'user.new.flash create success message', :email => @user.email
 -        Notifier.deliver_signup_confirm(@user, @user.tokens.create(:referer => params[:referer]))
 +        Notifier.deliver_signup_confirm(@user, @user.tokens.create(:referer => session.delete(:referer)))
          session[:token] = @user.tokens.create.token
          redirect_to :action => 'login', :referer => params[:referer]
        else
          @user.preferred_editor = params[:user][:preferred_editor]
        end
  
 -      if @user.save
 -        set_locale
 -
 -        if @user.new_email.nil? or @user.new_email.empty?
 -          flash[:notice] = t 'user.account.flash update success'
 -        else
 -          flash[:notice] = t 'user.account.flash update success confirm needed'
 +      @user.openid_url = nil if params[:user][:openid_url].empty?
  
 -          begin
 -            Notifier.deliver_email_confirm(@user, @user.tokens.create)
 -          rescue
 -            # Ignore errors sending email
 -          end
 -        end
 -
 -        redirect_to :action => "account", :display_name => @user.display_name
 +      if params[:user][:openid_url].length > 0 and
 +         params[:user][:openid_url] != @user.openid_url
 +        # If the OpenID has changed, we want to check that it is a
 +        # valid OpenID and one the user has control over before saving
 +        # it as a password equivalent for the user.
 +        session[:new_user] = @user
 +        openid_verify(params[:user][:openid_url], @user)
 +      else
 +        update_user(@user)
 +      end
 +    elsif using_open_id?
 +      # The redirect from the OpenID provider reenters here
 +      # again and we need to pass the parameters through to
 +      # the open_id_authentication function
 +      @user = session.delete(:new_user)
 +      openid_verify(nil, @user) do |user|
 +        update_user(user)
        end
      else
        if flash[:errors]
  
    def new
      @title = t 'user.new.title'
 -
 -    # The user is logged in already, so don't show them the signup
 -    # page, instead send them to the home page
 -    redirect_to :controller => 'site', :action => 'index' if session[:user]
 +    @referer = params[:referer] || session[:referer]
 +
 +    if session[:user]
 +      # The user is logged in already, so don't show them the signup
 +      # page, instead send them to the home page
 +      redirect_to :controller => 'site', :action => 'index'
 +    elsif not params['openid'].nil?
 +      flash.now[:notice] = t 'user.new.openid association'
 +    end
    end
  
    def login
 -    @title = t 'user.login.title'
 +    if params[:username] or using_open_id?
 +      session[:remember_me] ||= params[:remember_me]
 +      session[:referer] ||= params[:referer]
  
 -    if params[:user]
 -      email_or_display_name = params[:user][:email]
 -      pass = params[:user][:password]
 -      user = User.authenticate(:username => email_or_display_name, :password => pass)
 -
 -      if user
 -        session[:user] = user.id
 -        session_expires_after 1.month if params[:remember_me]
 -
 -        target = params[:referer] || url_for(:controller => :site, :action => :index)
 -
 -        # The user is logged in, so decide where to send them:
 -        #
 -        # - If they haven't seen the contributor terms, send them there.
 -        # - If they have a block on them, show them that.
 -        # - If they were referred to the login, send them back there.
 -        # - Otherwise, send them to the home page.
 -        if REQUIRE_TERMS_SEEN and not user.terms_seen
 -          redirect_to :controller => :user, :action => :terms, :referer => target
 -        elsif user.blocked_on_view
 -          redirect_to user.blocked_on_view, :referer => target
 -        else
 -          redirect_to target
 -        end
 -      elsif user = User.authenticate(:username => email_or_display_name, :password => pass, :pending => true)
 -        flash.now[:error] = t 'user.login.account not active', :reconfirm => url_for(:action => 'confirm_resend', :display_name => user.display_name)
 -      elsif User.authenticate(:username => email_or_display_name, :password => pass, :suspended => true)
 -        webmaster = link_to t('user.login.webmaster'), "mailto:webmaster@openstreetmap.org"
 -        flash.now[:error] = t 'user.login.account suspended', :webmaster => webmaster
 +      if using_open_id?
 +        openid_authentication(params[:openid_url])
        else
 -        flash.now[:error] = t 'user.login.auth failure'
 +        password_authentication(params[:username], params[:password])
        end
      elsif flash[:notice].nil?
        flash.now[:notice] =  t 'user.login.notice'
          if token
            token.destroy
          end
-         session[:token] = nil
+         session.delete(:token)
        end
-       session[:user] = nil
+       session.delete(:user)
        session_expires_automatically
        if params[:referer]
          redirect_to params[:referer]
  
  private
  
 +  ##
 +  # handle password authentication
 +  def password_authentication(username, password)
 +    if user = User.authenticate(:username => username, :password => password)
 +      successful_login(user)
 +    elsif user = User.authenticate(:username => username, :password => password, :pending => true)
 +      failed_login t('user.login.account not active', :reconfirm => url_for(:action => 'confirm_resend', :display_name => user.display_name))
 +    elsif User.authenticate(:username => username, :password => password, :suspended => true)
 +      webmaster = link_to t('user.login.webmaster'), "mailto:webmaster@openstreetmap.org"
 +      failed_login t('user.login.account suspended', :webmaster => webmaster)
 +    else
 +      failed_login t('user.login.auth failure')
 +    end
 +  end
 +
 +  ##
 +  # handle OpenID authentication
 +  def openid_authentication(openid_url)
 +    # If we don't appear to have a user for this URL then ask the
 +    # provider for some extra information to help with signup
 +    if openid_url and User.find_by_openid_url(openid_url)
 +      required = nil
 +    else
 +      required = [:nickname, :email, "http://axschema.org/namePerson/friendly", "http://axschema.org/contact/email"]
 +    end
 +
 +    # Start the authentication
 +    authenticate_with_open_id(openid_expand_url(openid_url), :required => required) do |result, identity_url, sreg, ax|
 +      if result.successful?
 +        # We need to use the openid url passed back from the OpenID provider
 +        # rather than the one supplied by the user, as these can be different.
 +        #
 +        # For example, you can simply enter yahoo.com in the login box rather
 +        # than a user specific url. Only once it comes back from the provider
 +        # provider do we know the unique address for the user.
 +        if user = User.find_by_openid_url(identity_url)
 +          case user.status
 +            when "pending" then
 +              failed_login t('user.login.account not active')
 +            when "active", "confirmed" then
 +              successful_login(user)
 +            when "suspended" then
 +              webmaster = link_to t('user.login.webmaster'), "mailto:webmaster@openstreetmap.org"
 +              failed_login t('user.login.account suspended', :webmaster => webmaster)
 +            else
 +              failed_login t('user.login.auth failure')
 +          end
 +        else
 +          # We don't have a user registered to this OpenID, so redirect
 +          # to the create account page with username and email filled
 +          # in if they have been given by the OpenID provider through
 +          # the simple registration protocol.
 +          nickname = sreg["nickname"] || ax["http://axschema.org/namePerson/friendly"]
 +          email = sreg["email"] || ax["http://axschema.org/contact/email"]
 +          redirect_to :controller => 'user', :action => 'new', :nickname => nickname, :email => email, :openid => identity_url
 +        end
 +      elsif result.missing?
 +        failed_login t('user.login.openid missing provider')
 +      elsif result.invalid?
 +        failed_login t('user.login.openid invalid')
 +      else
 +        failed_login t('user.login.auth failure')
 +      end
 +    end
 +  end
 +
 +  ##
 +  # verify an OpenID URL
 +  def openid_verify(openid_url, user)
 +    user.openid_url = openid_url
 +
 +    authenticate_with_open_id(openid_expand_url(openid_url)) do |result, identity_url|
 +      if result.successful?
 +        # We need to use the openid url passed back from the OpenID provider
 +        # rather than the one supplied by the user, as these can be different.
 +        #
 +        # For example, you can simply enter yahoo.com in the login box rather
 +        # than a user specific url. Only once it comes back from the provider
 +        # provider do we know the unique address for the user.
 +        user.openid_url = identity_url
 +        yield user
 +      elsif result.missing?
 +        flash.now[:error] = t 'user.login.openid missing provider'
 +      elsif result.invalid?
 +        flash.now[:error] = t 'user.login.openid invalid'
 +      else
 +        flash.now[:error] = t 'user.login.auth failure'
 +      end
 +    end
 +  end
 +
 +  ##
 +  # special case some common OpenID providers by applying heuristics to
 +  # try and come up with the correct URL based on what the user entered
 +  def openid_expand_url(openid_url)
 +    if openid_url.nil?
 +      return nil
 +    elsif openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/)
 +      # Special case gmail.com as it is potentially a popular OpenID
 +      # provider and, unlike yahoo.com, where it works automatically, Google
 +      # have hidden their OpenID endpoint somewhere obscure this making it
 +      # somewhat less user friendly.
 +      return 'https://www.google.com/accounts/o8/id'
 +    else
 +      return openid_url
 +    end
 +  end  
 +
 +  ##
 +  # process a successful login
 +  def successful_login(user)
 +    session[:user] = user.id
 +    session_expires_after 1.month if session[:remember_me]
 +
 +    target = session[:referer] || url_for(:controller => :site, :action => :index)
 +
 +    # The user is logged in, so decide where to send them:
 +    #
 +    # - If they haven't seen the contributor terms, send them there.
 +    # - If they have a block on them, show them that.
 +    # - If they were referred to the login, send them back there.
 +    # - Otherwise, send them to the home page.
 +    if REQUIRE_TERMS_SEEN and not user.terms_seen
 +      redirect_to :controller => :user, :action => :terms, :referer => target
 +    elsif user.blocked_on_view
 +      redirect_to user.blocked_on_view, :referer => target
 +    else
 +      redirect_to target
 +    end
 +
 +    session.delete(:remember_me)
 +    session.delete(:referer)
 +  end
 +
 +  ##
 +  # process a failed login
 +  def failed_login(message)
 +    flash[:error] = message
 +
 +    redirect_to :action => 'login', :referer =>  session[:referer]
 +
 +    session.delete(:remember_me)
 +    session.delete(:referer)
 +  end
 +
 +  ##
 +  # update a user's details
 +  def update_user(user)
 +    if user.save
 +      set_locale
 +
 +      if user.new_email.nil? or user.new_email.empty?
 +        flash.now[:notice] = t 'user.account.flash update success'
 +      else
 +        flash.now[:notice] = t 'user.account.flash update success confirm needed'
 +
 +        begin
 +          Notifier.deliver_email_confirm(user, user.tokens.create)
 +        rescue
 +          # Ignore errors sending email
 +        end
 +      end
 +    end
 +  end
 +
    ##
    # require that the user is a administrator, or fill out a helpful error message
    # and return them to the user page.
diff --combined app/models/user.rb
index 79af5d71f66c1836e69213aa812c7af32b66fcb2,a817c0fcf404c5c518bd0f0fe613ca1fdde6e10d..0b2a902dfac734f71d18c183cefe5863e579bbe1
@@@ -23,14 -23,13 +23,14 @@@ class User < ActiveRecord::Bas
    validates_confirmation_of :pass_crypt#, :message => ' must match the confirmation password'
    validates_uniqueness_of :display_name, :allow_nil => true
    validates_uniqueness_of :email
 +  validates_uniqueness_of :openid_url, :allow_nil => true
    validates_length_of :pass_crypt, :within => 8..255
    validates_length_of :display_name, :within => 3..255, :allow_nil => true
-   validates_email_format_of :email
-   validates_email_format_of :new_email, :allow_blank => true
-   validates_format_of :display_name, :with => /^[^\/;.,?]*$/
-   validates_format_of :display_name, :with => /^\S/, :message => "has leading whitespace"
-   validates_format_of :display_name, :with => /\S$/, :message => "has trailing whitespace"
+   validates_email_format_of :email, :if => Proc.new { |u| u.email_changed? }
+   validates_email_format_of :new_email, :allow_blank => true, :if => Proc.new { |u| u.new_email_changed? }
+   validates_format_of :display_name, :with => /^[^\/;.,?]*$/, :if => Proc.new { |u| u.display_name_changed? }
+   validates_format_of :display_name, :with => /^\S/, :message => "has leading whitespace", :if => Proc.new { |u| u.display_name_changed? }
+   validates_format_of :display_name, :with => /\S$/, :message => "has trailing whitespace", :if => Proc.new { |u| u.display_name_changed? }
    validates_numericality_of :home_lat, :allow_nil => true
    validates_numericality_of :home_lon, :allow_nil => true
    validates_numericality_of :home_zoom, :only_integer => true, :allow_nil => true
index 0616d76f8aa23c4c31ebb7f09b853aaa42457860,8fe094cfa7b404075cab725a0309d5bd9acdf0e8..6297b219db58512a29f0973f6def39954b54cf1f
  <div id="login_wrapper">
 +
    <div id="login_login">
      <h1><%= t 'user.login.heading' %></h1>
  
 -    <p><%= t 'user.login.already have' %></p>
 -
 -    <% form_tag :action => 'login' do %>
 +    <% form_tag({ :action => "login" }, { :id => "login_form" }) do %>
        <%= hidden_field_tag('referer', h(params[:referer])) %>
 +
 +      <p><%= t 'user.login.with username' %></p>
 +
        <table id="loginForm">
-         <tr><td class="fieldName"><%= t 'user.login.email or username' %></td><td><%= text_field('user', 'email',{:value => "", :size => 28, :maxlength => 255, :tabindex => 1}) %></td></tr>
+         <tr><td class="fieldName"><%= t 'user.login.email or username' %></td><td><%= text_field('user', 'email',{:value => params[:username], :size => 28, :maxlength => 255, :tabindex => 1}) %></td></tr>
          <tr><td class="fieldName"><%= t 'user.login.password' %></td><td><%= password_field('user', 'password',{:value => "", :size => 28, :maxlength => 255, :tabindex => 2}) %> <span class="minorNote">(<%= link_to t('user.login.lost password link'), :controller => 'user', :action => 'lost_password' %>)</span></td></tr>
          <tr><td class="fieldName"><label for="remember_me"><%= t 'user.login.remember' %></label></td><td><%= check_box_tag "remember_me", "yes", false, :tabindex => 3 %></td></tr>
        </table>
        <%= submit_tag t('user.login.login_button'), :tabindex => 3 %>
 +
 +      <br clear="all" />
 +
 +      <p><%= t 'user.login.with openid' %></p>
 +
 +      <table id="login_openid_buttons_wide">
 +        <tr>
 +          <td>
 +            <%=
 +              link_to_function(image_tag("openid_large.png", :alt => t("user.login.openid_providers.openid.title")), nil, :title => t("user.login.openid_providers.openid.title")) do |page|
 +                page[:login_form][:openid_url].value = "http://"
 +                page[:login_openid_buttons_wide].hide
 +                page[:login_openid_url].show
 +                page[:login_openid_submit].show
 +              end
 +            %>
 +          </td>
 +          <td><%= openid_button "google", "gmail.com" %></td>
 +          <td><%= openid_button "yahoo", "me.yahoo.com" %></td>
 +        </tr>
 +        <tr>
 +          <td><%= openid_button "myopenid", "myopenid.com" %></td>
 +          <td><%= openid_button "wordpress", "wordpress.com" %></td>
 +          <td><%= openid_button "aol", "aol.com" %></td>
 +        </tr>
 +      </table>
 +
 +      <table id="login_openid_buttons_narrow">
 +        <tr>
 +          <td>
 +            <%=
 +              link_to_function(image_tag("openid_large.png", :alt => t("user.login.openid_providers.openid.title")), nil, :title => t("user.login.openid_providers.openid.title")) do |page|
 +                page[:login_form][:openid_url].value = "http://"
 +                page[:login_openid_buttons_narrow].hide
 +                page[:login_openid_url].show
 +                page[:login_openid_submit].show
 +              end
 +            %>
 +          </td>
 +          <td><%= openid_button "google", "gmail.com" %></td>
 +        </tr>
 +        <tr>
 +          <td><%= openid_button "yahoo", "me.yahoo.com" %></td>
 +          <td><%= openid_button "myopenid", "myopenid.com" %></td>
 +        </tr>
 +        <tr>
 +          <td><%= openid_button "wordpress", "wordpress.com" %></td>
 +          <td><%= openid_button "aol", "aol.com" %></td>
 +        </tr>
 +      </table>
 +
 +      <table>
 +        <tr id="login_openid_url">
 +          <td class="fieldName nowrap">
 +            <%= t 'user.login.openid', :logo => openid_logo %>
 +          </td>
 +          <td>
 +            <%= text_field_tag("openid_url", "", { :size => 28, :maxlength => 255, :tabindex => 3, :class => "openid_url" }) %>
 +            <span class="minorNote">(<a href="<%= t 'user.account.openid.link' %>" target="_new"><%= t 'user.account.openid.link text' %></a>)</span>
 +          </td>
 +        </tr>
 +        <tr>
 +          <td class="fieldName nowrap" id="remember_me_label"><label for="remember_me"><%= t 'user.login.remember' %></label></td>
 +          <td width="100%"><%= check_box_tag "remember_me", "yes", false, :tabindex => 5 %></td>
 +        </tr>
 +      </table>
 +
 +      <%= submit_tag t('user.login.login_button'), :tabindex => 6, :id => "login_openid_submit" %>
      <% end %>
 +
      <br clear="all" />
    </div>
 +
    <div id="login_signup">
      <h2><%= t 'user.login.new to osm' %></h2>
      <p><%= t 'user.login.to make changes' %></p>
      <p><%= t 'user.login.create account minute' %></p>
      <p><%= button_to t('user.login.register now'), :action => :new, :referer => params[:referer] %></p>
 -    <br clear="all" />
 +
 +    <br clear="both">
    </div>
 +
  </div>
 +
 +<%=
 +  update_page_tag do |page|
 +    page[:login_openid_url].hide
 +    page[:login_openid_submit].hide
 +  end
 +%>
diff --combined config/locales/en.yml
index 30901e50a705f47362c40af7286ef35d41fd2731,48eb9ce1269f239f53243c3602b51bda88daf84e..e41048d0fb0259da4112c6ad6601dc8abbc5a078
@@@ -1056,6 -1056,9 +1056,9 @@@ en
        <ul id="contributors">
            <li><strong>Australia</strong>: Contains suburb data based
            on Australian Bureau of Statistics data.</li>
+           <li><strong>Austria</strong>: Contains data from
+           <a href="http://data.wien.gv.at/">Stadt Wien</a> under
+           <a href="http://creativecommons.org/licenses/by/3.0/at/deed.de">CC-BY</a>.</li>
            <li><strong>Canada</strong>: Contains data from
            GeoBase&reg;, GeoGratis (&copy; Department of Natural
            Resources Canada), CanVec (&copy; Department of Natural
      login:
        title: "Login"
        heading: "Login"
 -      please login: "Please login or %{create_user_link}."
 -      create_account: "create an account"
        email or username: "Email Address or Username:"
        password: "Password:"
 +      openid: "%{logo} OpenID:"
        remember: "Remember me:"
        lost password link: "Lost your password?"
        login_button: "Login"
        register now: Register now
 -      already have: Already have an OpenStreetMap account? Please login.
 +      with username: "Already have an OpenStreetMap account? Please login with your username and password:"
 +      with openid: "Alternatively please use your OpenID to login:"
        new to osm: New to OpenStreetMap?
        to make changes: To make changes to the OpenStreetMap data, you must have an account.
        create account minute: Create an account. It only takes a minute.
        webmaster: webmaster
        auth failure: "Sorry, could not log in with those details."
        notice: "<a href=\"http://www.osmfoundation.org/wiki/License/We_Are_Changing_The_License\">Find out more about OpenStreetMap's upcoming license change</a> (<a href=\"http://wiki.openstreetmap.org/wiki/ODbL/We_Are_Changing_The_License\">translations</a>) (<a href=\"http://wiki.openstreetmap.org/wiki/Talk:ODbL/Upcoming\">discussion</a>)"
 +      openid missing provider: "Sorry, could not contact your OpenID provider"
 +      openid invalid: "Sorry, your OpenID seems to be malformed"
 +      openid_logo_alt: "Log in with an OpenID"
 +      openid_providers:
 +        openid:
 +          title: Login with OpenID
 +          alt: Login with an OpenID URL
 +        google:
 +          title: Login with Google
 +          alt: Login with a Google OpenID
 +        yahoo:
 +          title: Login with Yahoo
 +          alt: Login with a Yahoo OpenID
 +        myopenid:
 +          title: Login with myOpenID
 +          alt: Login with a myOpenID OpenID
 +        wordpress:
 +          title: Login with Wordpress
 +          alt: Login with a Wordpress OpenID
 +        aol:
 +          title: Login with AOL
 +          alt: Login with an AOL OpenID
      logout:
        title: "Logout"
        heading: "Logout from OpenStreetMap"
        not displayed publicly: 'Not displayed publicly (see <a href="http://wiki.openstreetmap.org/wiki/Privacy_Policy" title="wiki privacy policy including section on email addresses">privacy policy</a>)'
        display name: "Display Name:"
        display name description: "Your publicly displayed username. You can change this later in the preferences."
 +      openid: "%{logo} OpenID:"
        password: "Password:"
        confirm password: "Confirm Password:"
 +      use openid: "Alternatively, use %{logo} OpenID to login"
 +      openid no password: "With OpenID a password is not required, but some extra tools or server may still need one."
 +      openid association: |
 +        <p>Your OpenID is not associated with a OpenStreetMap account yet.</p>
 +        <ul>
 +          <li>If you are new to OpenStreetMap, please create a new account using the form below.</li>
 +          <li>
 +            If you already have an account, you can login to your account
 +            using your username and password and then associate the account
 +            with your OpenID in your user settings.
 +          </li>
 +        </ul> 
        continue: Continue
        flash create success message: "Thanks for signing up. We've sent a confirmation note to %{email} and as soon as you confirm your account you'll be able to get mapping.<br /><br />If you use an antispam system which sends confirmation requests then please make sure you whitelist webmaster@openstreetmap.org as we are unable to reply to any confirmation requests."
        terms accepted: "Thanks for accepting the new contributor terms!"
        current email address: "Current Email Address:"
        new email address: "New Email Address:"
        email never displayed publicly: "(never displayed publicly)"
 +      openid:
 +        openid: "OpenID:"
 +        link: "http://wiki.openstreetmap.org/wiki/OpenID"
 +        link text: "what is this?"
        public editing:
          heading: "Public editing:"
          enabled: "Enabled. Not anonymous and can edit data."