Index: modules/taxonomy/taxonomy.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.admin.inc,v
retrieving revision 1.37
diff -u -p -r1.37 taxonomy.admin.inc
--- modules/taxonomy/taxonomy.admin.inc	13 Nov 2008 08:13:56 -0000	1.37
+++ modules/taxonomy/taxonomy.admin.inc	10 Dec 2008 12:00:25 -0000
@@ -160,14 +160,14 @@ function taxonomy_form_vocabulary(&$form
     '#collapsible' => TRUE,
   );
   $form['settings']['tags'] = array('#type' => 'checkbox',
-    '#title' => t('Tags'),
+    '#title' => t('Autocomplete'),
     '#default_value' => $edit['tags'],
-    '#description' => t('Terms are created by users when submitting posts by typing a comma separated list.'),
+    '#description' => t('Use autocomplete selection when posting content. Users with the appropriate permission may create new terms.'),
   );
   $form['settings']['multiple'] = array('#type' => 'checkbox',
     '#title' => t('Multiple select'),
     '#default_value' => $edit['multiple'],
-    '#description' => t('Allows posts to have more than one term from this vocabulary (always true for tags).'),
+    '#description' => t('Allows posts to have more than one term from this vocabulary (always true for autocomplete).'),
   );
   $form['settings']['required'] = array('#type' => 'checkbox',
     '#title' => t('Required'),
@@ -701,12 +701,16 @@ function taxonomy_form_term(&$form_state
     '#value' => t('Save'));
 
   if ($edit['tid']) {
-    $form['delete'] = array(
-      '#type' => 'submit',
-      '#value' => t('Delete'));
+    if (user_access("delete terms in $vocabulary->vid") || user_access('administer taxonomy')) {
+      $form['delete'] = array(
+        '#type' => 'submit',
+        '#value' => t('Delete'),
+      );
+    }
     $form['tid'] = array(
       '#type' => 'value',
-      '#value' => $edit['tid']);
+      '#value' => $edit['tid'],
+    );
   }
   else {
     $form['destination'] = array('#type' => 'hidden', '#value' => $_GET['q']);
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.447
diff -u -p -r1.447 taxonomy.module
--- modules/taxonomy/taxonomy.module	9 Dec 2008 11:30:25 -0000	1.447
+++ modules/taxonomy/taxonomy.module	10 Dec 2008 12:00:26 -0000
@@ -10,12 +10,33 @@
  * Implementation of hook_perm().
  */
 function taxonomy_perm() {
-  return array(
+  $permissions = array(
     'administer taxonomy' => array(
       'title' => t('Administer taxonomy'),
       'description' => t('Manage taxonomy vocabularies and terms.'),
     ),
   );
+  foreach (taxonomy_get_vocabularies() as $vocabulary) {
+    $permissions += array(
+      'create terms in ' . $vocabulary->vid => array(
+        'title' => t('Create terms in %vocabulary', array('%vocabulary' => $vocabulary->name)),
+        'description' => t('Create terms in the %vocabulary vocabulary.', array('%vocabulary' => $vocabulary->name)),
+      ),
+    );
+    $permissions += array(
+      'edit terms in ' . $vocabulary->vid => array(
+        'title' => t('Edit terms in %vocabulary', array('%vocabulary' => $vocabulary->name)),
+        'description' => t('Edit terms in the %vocabulary vocabulary.', array('%vocabulary' => $vocabulary->name)),
+      ),
+    );
+    $permissions += array(
+      'delete terms in ' . $vocabulary->vid => array(
+        'title' => t('Delete terms in %vocabulary', array('%vocabulary' => $vocabulary->name)),
+        'description' => t('Delete terms in the %vocabulary vocabulary.', array('%vocabulary' => $vocabulary->name)),
+      ),
+    );
+  }
+  return $permissions;
 }
 
 /**
@@ -153,7 +174,8 @@ function taxonomy_menu() {
     'title' => 'Edit term',
     'page callback' => 'taxonomy_term_edit',
     'page arguments' => array(2),
-    'access arguments' => array('administer taxonomy'),
+    'access callback' => 'taxonomy_term_edit_access',
+    'access arguments' => array(2),
     'type' => MENU_LOCAL_TASK,
     'weight' => 10,
   );
@@ -209,6 +231,13 @@ function taxonomy_admin_vocabulary_title
 }
 
 /**
+ * Return edit access for a given term.
+ */
+function taxonomy_term_edit_access($term) {
+  return user_access("edit terms in $term->vid") || user_access('administer taxonomy');
+}
+
+/**
  * Save a vocabulary given a vocabulary object.
  */
 function taxonomy_vocabulary_save($vocabulary) {
@@ -666,25 +695,6 @@ function taxonomy_node_get_terms($node, 
 }
 
 /**
- * Make sure incoming vids are free tagging enabled.
- */
-function taxonomy_node_validate(&$node) {
-  if (!empty($node->taxonomy)) {
-    $terms = $node->taxonomy;
-    if (!empty($terms['tags'])) {
-      foreach ($terms['tags'] as $vid => $vid_value) {
-        $vocabulary = taxonomy_vocabulary_load($vid);
-        if (empty($vocabulary->tags)) {
-          // see form_get_error $key = implode('][', $element['#parents']);
-          // on why this is the key
-          form_set_error("taxonomy][tags][$vid", t('The %name vocabulary can not be modified in this way.', array('%name' => $vocabulary->name)));
-        }
-      }
-    }
-  }
-}
-
-/**
  * Save term associations for a given node.
  */
 function taxonomy_node_save($node, $terms) {
@@ -699,6 +709,7 @@ function taxonomy_node_save($node, $term
 
     foreach ($typed_input as $vid => $vid_value) {
       $typed_terms = drupal_explode_tags($vid_value);
+      $rejected_terms = array();
 
       $inserted = array();
       foreach ($typed_terms as $typed_term) {
@@ -713,18 +724,25 @@ function taxonomy_node_save($node, $term
         }
 
         if (!$typed_term_tid) {
-          $edit = array('vid' => $vid, 'name' => $typed_term);
-          $term = (object)$edit;
-          $status = taxonomy_term_save($term);
-          $typed_term_tid = $term->tid;
+          if (user_access("create terms in $vid") || user_access('administer taxonomy')) {
+            $edit = array('vid' => $vid, 'name' => $typed_term);
+            $term = (object)$edit;
+            $status = taxonomy_term_save($term);
+            $typed_term_tid = $term->tid;
+          }
+          else {
+            $rejected_terms[] = $typed_term;
+          }
         }
-
         // Defend against duplicate, differently cased tags
         if (!isset($inserted[$typed_term_tid])) {
           db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $node->nid, $node->vid, $typed_term_tid);
           $inserted[$typed_term_tid] = TRUE;
         }
       }
+      if (!empty($rejected_terms)) {
+        drupal_set_message(t('You do not permissions to save the terms: %terms.', array('%terms' => implode(', ', $rejected_terms))));
+      }
     }
   }
 
@@ -1397,13 +1415,6 @@ function taxonomy_nodeapi_delete_revisio
 }
 
 /**
- * Implementation of hook_nodeapi_validate().
- */
-function taxonomy_nodeapi_validate($node, $form) {
-  taxonomy_node_validate($node);
-}
-
-/**
  * Implementation of hook_nodeapi_rss_item().
  */
 function taxonomy_nodeapi_rss_item($node) {
Index: modules/taxonomy/taxonomy.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v
retrieving revision 1.17
diff -u -p -r1.17 taxonomy.test
--- modules/taxonomy/taxonomy.test	5 Dec 2008 22:18:46 -0000	1.17
+++ modules/taxonomy/taxonomy.test	10 Dec 2008 12:00:26 -0000
@@ -251,9 +251,10 @@ class TaxonomyTermTestCase extends Taxon
 
   function setUp() {
     parent::setUp('taxonomy');
+    $this->vocabulary = $this->createVocabulary();
     $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access'));
+    $this->normal_user = $this->drupalCreateUser(array('create article content'));
     $this->drupalLogin($this->admin_user);
-    $this->vocabulary = $this->createVocabulary();
   }
 
   /**
@@ -383,10 +384,20 @@ class TaxonomyTermTestCase extends Taxon
     $edit['taxonomy[tags][' . $this->vocabulary->vid .']'] =  implode(', ', $terms);
     $edit['body'] = $this->randomName();
     $this->drupalPost('node/add/article', $edit, t('Save'));
-    $this->assertRaw(t('@type %title has been created.', array('@type' => t('Article'), '%title' => $edit['title'])), t('The node was created successfully'));
+    $this->assertRaw(t('@type %title has been created.', array('@type' => t('Article'), '%title' => $edit['title'])), t('The node was created successfully.'));
     foreach ($terms as $term) {
       $this->assertText($term, t('The term was saved and appears on the node page'));
     }
+
+    // Test attempted creation of tags by a user without sufficient permissions.
+    $this->drupalLogout();
+    $this->drupalLogin($this->normal_user);
+    $extra_term = $this->randomName();
+    $terms[] = $extra_term;
+    $edit['taxonomy[tags][' . $this->vocabulary->vid .']'] = implode(', ', $terms);
+    $this->drupalPost('node/add/article', $edit, t('Save'));
+    $this->assertRaw(t('You do not permissions to save the terms: %term.', array('%term' => $extra_term)), t('Term was rejected.'));
+    $this->assertFalse(taxonomy_get_term_by_name($extra_term), t('Term was not saved in the database.'));
   }
 
   /**
