├── .gitignore
├── LICENSE
├── ReadMe.md
├── SharePoint-Add-in-REST-OData-BasicDataOperations.sln
├── SharePoint-Add-in-REST-OData-BasicDataOperations
├── AppIcon.png
├── AppManifest.xml
└── SharePoint-Add-in-REST-OData-BasicDataOperations.csproj
├── SharePoint-Add-in-REST-OData-BasicDataOperationsWeb
├── Pages
│ ├── Default.aspx
│ ├── Default.aspx.cs
│ └── Default.aspx.designer.cs
├── Properties
│ └── AssemblyInfo.cs
├── SharePoint-Add-in-REST-OData-BasicDataOperationsWeb.csproj
├── SharePointContext.cs
├── TokenHelper.cs
├── Web.Debug.config
├── Web.Release.config
├── Web.config
└── packages.config
└── description
└── fig1.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studo 2015 cache/options directory
26 | .vs/
27 |
28 | # MSTest test Results
29 | [Tt]est[Rr]esult*/
30 | [Bb]uild[Ll]og.*
31 |
32 | # NUNIT
33 | *.VisualState.xml
34 | TestResult.xml
35 |
36 | # Build Results of an ATL Project
37 | [Dd]ebugPS/
38 | [Rr]eleasePS/
39 | dlldata.c
40 |
41 | *_i.c
42 | *_p.c
43 | *_i.h
44 | *.ilk
45 | *.meta
46 | *.obj
47 | *.pch
48 | *.pdb
49 | *.pgc
50 | *.pgd
51 | *.rsp
52 | *.sbr
53 | *.tlb
54 | *.tli
55 | *.tlh
56 | *.tmp
57 | *.tmp_proj
58 | *.log
59 | *.vspscc
60 | *.vssscc
61 | .builds
62 | *.pidb
63 | *.svclog
64 | *.scc
65 |
66 | # Chutzpah Test files
67 | _Chutzpah*
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 | *.cachefile
76 |
77 | # Visual Studio profiler
78 | *.psess
79 | *.vsp
80 | *.vspx
81 |
82 | # TFS 2012 Local Workspace
83 | $tf/
84 |
85 | # Guidance Automation Toolkit
86 | *.gpState
87 |
88 | # ReSharper is a .NET coding add-in
89 | _ReSharper*/
90 | *.[Rr]e[Ss]harper
91 | *.DotSettings.user
92 |
93 | # JustCode is a .NET coding addin-in
94 | .JustCode
95 |
96 | # TeamCity is a build add-in
97 | _TeamCity*
98 |
99 | # DotCover is a Code Coverage Tool
100 | *.dotCover
101 |
102 | # NCrunch
103 | _NCrunch_*
104 | .*crunch*.local.xml
105 |
106 | # MightyMoose
107 | *.mm.*
108 | AutoTest.Net/
109 |
110 | # Web workbench (sass)
111 | .sass-cache/
112 |
113 | # Installshield output folder
114 | [Ee]xpress/
115 |
116 | # DocProject is a documentation generator add-in
117 | DocProject/buildhelp/
118 | DocProject/Help/*.HxT
119 | DocProject/Help/*.HxC
120 | DocProject/Help/*.hhc
121 | DocProject/Help/*.hhk
122 | DocProject/Help/*.hhp
123 | DocProject/Help/Html2
124 | DocProject/Help/html
125 |
126 | # Click-Once directory
127 | publish/
128 |
129 | # Publish Web Output
130 | *.[Pp]ublish.xml
131 | *.azurePubxml
132 | # TODO: Comment the next line if you want to checkin your web deploy settings
133 | # but database connection strings (with potential passwords) will be unencrypted
134 | *.pubxml
135 | *.publishproj
136 |
137 | # NuGet Packages
138 | *.nupkg
139 | # The packages folder can be ignored because of Package Restore
140 | **/packages/*
141 | # except build/, which is used as an MSBuild target.
142 | !**/packages/build/
143 | # Uncomment if necessary however generally it will be regenerated when needed
144 | #!**/packages/repositories.config
145 |
146 | # Windows Azure Build Output
147 | csx/
148 | *.build.csdef
149 |
150 | # Windows Store app package directory
151 | AppPackages/
152 |
153 | # Others
154 | *.[Cc]ache
155 | ClientBin/
156 | [Ss]tyle[Cc]op.*
157 | ~$*
158 | *~
159 | *.dbmdl
160 | *.dbproj.schemaview
161 | *.pfx
162 | *.publishsettings
163 | node_modules/
164 | bower_components/
165 |
166 | # RIA/Silverlight projects
167 | Generated_Code/
168 |
169 | # Backup & report files from converting an old project file
170 | # to a newer Visual Studio version. Backup files are not needed,
171 | # because we have git ;-)
172 | _UpgradeReport_Files/
173 | Backup*/
174 | UpgradeLog*.XML
175 | UpgradeLog*.htm
176 |
177 | # SQL Server files
178 | *.mdf
179 | *.ldf
180 |
181 | # Business Intelligence projects
182 | *.rdl.data
183 | *.bim.layout
184 | *.bim_*.settings
185 |
186 | # Microsoft Fakes
187 | FakesAssemblies/
188 |
189 | # Node.js Tools for Visual Studio
190 | .ntvs_analysis.dat
191 |
192 | # Visual Studio 6 build log
193 | *.plg
194 |
195 | # Visual Studio 6 workspace options file
196 | *.opt
197 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Office
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/ReadMe.md:
--------------------------------------------------------------------------------
1 | ---
2 | page_type: sample
3 | products:
4 | - office-sp
5 | - office-365
6 | languages:
7 | - csharp
8 | urlFragment: crud-operations-sharepoint
9 | description: "Use the SharePoint REST/OData endpoints to perform operations on lists and list items from a SharePoint Add-in."
10 | extensions:
11 | contentType: samples
12 | technologies:
13 | - Add-ins
14 | createdDate: "8/8/2015 11:33:40 AM"
15 | ---
16 |
17 | # Basic CRUD operations in SharePoint Add-ins using the REST/OData APIs
18 |
19 | > SharePoint add-in model is considered as a legacy option for extending SharePoint user interface. Please see [SharePoint Framework documentation](https://aka.ms/spfx) and the [SharePoint Framework samples](https://aka.ms/spfx-webparts) for the future proven option to extend SharePoint Online. Possible backend services should be using Azure Active Directly based registration and related app models.
20 |
21 | ## Summary
22 | Use the SharePoint REST/OData endpoints to perform create, read, update, and delete operations on lists and list items from a SharePoint Add-in.
23 |
24 | ### Applies to
25 | - SharePoint Online and on-premise SharePoint 2013 and later
26 |
27 | ## Prerequisites
28 | This sample requires the following:
29 |
30 |
31 | - A SharePoint 2013 (or later) development environment that is configured for add-in isolation and OAuth. (A SharePoint Online Developer Site is automatically configured. For an on premise development environment, see [Set up an on-premises development environment for SharePoint Add-ins](https://msdn.microsoft.com/library/office/fp179923.aspx) and [Use an Office 365 SharePoint site to authorize provider-hosted add-ins on an on-premises SharePoint site](https://msdn.microsoft.com/library/office/dn155905.aspx).)
32 |
33 |
34 | - Visual Studio and the Office Developer Tools for Visual Studio installed on your developer computer
35 |
36 |
37 | - Basic familiarity with RESTful web services and OData
38 |
39 | ## Description of the code ##
40 | The code that uses the REST APIs is located in the Default.aspx.cs file of the SharePoint-Add-in-REST-OData-BasicDataOperationsWeb project. The Default.aspx page of the add-in appears after you install and launch the add-in and looks similar to the following.
41 |
42 | 
43 |
44 |
45 |
46 | The sample demonstrates the following:
47 |
48 |
49 | - How to read and write data to and from a SharePoint host web. This data conforms with the OData protocol to the REST endpoints where the list and list item entities are exposed.
50 |
51 |
52 |
53 | - How to parse Atom-formatted XML returned from these endpoints and how to construct JSON-formatted representations of the list and list item entities so that you can perform Create and Update operations on them.
54 |
55 |
56 | - Best practices for retrieving form digest and eTag values that are required for Create and Update operations on lists and list items.
57 |
58 |
59 | ## To use the sample #
60 |
61 | 12. Open **Visual Studio** as an administrator.
62 | 13. Open the .sln file.
63 | 13. In **Solution Explorer**, highlight the SharePoint add-in project and replace the **Site URL** property with the URL of your SharePoint developer site.
64 | 14. Press F5.
65 | 15. After the add-in installs, the consent page opens. Click **Trust It**.
66 | 16. Enter a string in the text box beside the **Add List** button and click the button. In a moment, the page refreshes and the new list is in the table.
67 | 17. Click the ID of the list, and then click **Retrieve List Items**. There will initially be no items on the list. Some additional buttons will appear.
68 | 18. Add a string to the text box beside the **Add Item** button and press the button. The new item will appear in the table in the row for the list.
69 | 19. Add a string to the text box beside the **Change List Title** button and press the button. The title will change in the table.
70 | 20. Press the **Delete the List** button and the list is deleted.
71 |
72 | **Do not delete any of the built-in SharePoint lists. If you mistakenly do so, recover the list from the SharePoint Recycle Bin.**
73 |
74 | ## Troubleshooting
75 |
76 |
77 |
78 |
79 | Problem
80 | |
81 | Solution |
82 |
83 |
84 | Visual Studio does not open the browser after you press the F5 key. |
85 | Set the SharePoint Add-in project as the startup project. |
86 |
87 |
88 | HTTP error 405 Method not allowed. |
89 | Locate the applicationhost.config file in %userprofile%\Documents\IISExpress\config.
90 | Locate the handler entry for StaticFile, and add the verbs
91 | GET, HEAD, POST, DEBUG, and
92 | TRACE.
93 | |
94 |
95 |
96 |
97 |
98 | ## Questions and comments
99 |
100 | We'd love to get your feedback on this sample. You can send your questions and suggestions to us in the [Issues](https://github.com/OfficeDev/SharePoint-Add-in-REST-OData-BasicDataOperations/issues) section of this repository.
101 |
102 | ## Additional resources
103 |
104 | [Get to know the SharePoint 2013 REST service](https://msdn.microsoft.com/library/fp142380.aspx).
105 |
106 | [Open Data Protocol](http://www.odata.org/)
107 |
108 | [OData: JavaScript Object Notation (JSON) Format](http://www.odata.org/developers/protocols/json-format)
109 |
110 | [OData: AtomPub Format](http://www.odata.org/developers/protocols/atom-format).
111 |
112 | ### Copyright
113 |
114 | Copyright (c) Microsoft. All rights reserved.
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperations.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.40629.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharePoint-Add-in-REST-OData-BasicDataOperations", "SharePoint-Add-in-REST-OData-BasicDataOperations\SharePoint-Add-in-REST-OData-BasicDataOperations.csproj", "{CDE76E8E-DCB8-47E7-A63A-70A0CDB88999}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharePoint-Add-in-REST-OData-BasicDataOperationsWeb", "SharePoint-Add-in-REST-OData-BasicDataOperationsWeb\SharePoint-Add-in-REST-OData-BasicDataOperationsWeb.csproj", "{32170F89-0BF8-4017-9846-D2C0D81581AE}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {CDE76E8E-DCB8-47E7-A63A-70A0CDB88999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {CDE76E8E-DCB8-47E7-A63A-70A0CDB88999}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {CDE76E8E-DCB8-47E7-A63A-70A0CDB88999}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
19 | {CDE76E8E-DCB8-47E7-A63A-70A0CDB88999}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {CDE76E8E-DCB8-47E7-A63A-70A0CDB88999}.Release|Any CPU.Build.0 = Release|Any CPU
21 | {CDE76E8E-DCB8-47E7-A63A-70A0CDB88999}.Release|Any CPU.Deploy.0 = Release|Any CPU
22 | {32170F89-0BF8-4017-9846-D2C0D81581AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {32170F89-0BF8-4017-9846-D2C0D81581AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {32170F89-0BF8-4017-9846-D2C0D81581AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {32170F89-0BF8-4017-9846-D2C0D81581AE}.Release|Any CPU.Build.0 = Release|Any CPU
26 | EndGlobalSection
27 | GlobalSection(SolutionProperties) = preSolution
28 | HideSolutionNode = FALSE
29 | EndGlobalSection
30 | EndGlobal
31 |
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperations/AppIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/SharePoint-Add-in-REST-OData-BasicDataOperations/b700eb7c6114ffe3288f72398ea55db53dc3ea0e/SharePoint-Add-in-REST-OData-BasicDataOperations/AppIcon.png
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperations/AppManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 | SharePoint-Add-in-REST-OData-BasicDataOperations
11 | ~remoteAppUrl/Pages/Default.aspx?{StandardTokens}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperations/SharePoint-Add-in-REST-OData-BasicDataOperations.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {CDE76E8E-DCB8-47E7-A63A-70A0CDB88999}
8 | Library
9 | Properties
10 | SharePoint_Add_in_REST_OData_BasicDataOperations
11 | SharePoint-Add-in-REST-OData-BasicDataOperations
12 | v4.5
13 | 15.0
14 | 512
15 | {C1CDDADD-2546-481F-9697-4EA41081F2FC};{14822709-B5A1-4724-98CA-57A101D1B079};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
16 | 11.0
17 | 11.1
18 | False
19 | SharePointApp
20 | {b2ab3bd3-bc76-401a-be6a-6e748719b823}
21 | {696a47f2-80d8-45ec-a6f7-38ea5b62336a}
22 | {147697fd-fe77-4291-903b-e84172518a69}
23 | {4d3a78d9-a3f6-4d91-9eaf-6f003a345bd4}
24 | {98576287-576d-451d-9e57-e796a9ee5a9f}
25 |
26 |
27 | true
28 | full
29 | false
30 | bin\Debug\
31 | DEBUG;TRACE
32 | prompt
33 | 4
34 | false
35 |
36 |
37 | pdbonly
38 | true
39 | bin\Release\
40 | TRACE
41 | prompt
42 | 4
43 | false
44 |
45 |
46 |
47 | manifest-icon
48 |
49 |
50 |
51 |
52 | Designer
53 |
54 |
55 |
56 |
57 | {32170F89-0BF8-4017-9846-D2C0D81581AE}
58 | SharePoint-Add-in-REST-OData-BasicDataOperationsWeb
59 | True
60 | Web
61 | SharePointWebProjectOutput
62 | SharePoint-Add-in-REST-OData-BasicDataOperationsWeb
63 | False
64 |
65 |
66 |
67 |
68 | 10.0
69 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
70 |
71 |
72 |
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb/Pages/Default.aspx:
--------------------------------------------------------------------------------
1 | <%-- Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file. --%>
2 |
3 | <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="SharePoint_Add_in_REST_OData_BasicDataOperationsWeb.Default" %>
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
32 |
33 |
34 |
35 | <%--
36 | SharePoint Add-in REST/OData Basic Data Operations, https://github.com/OfficeDev/SharePoint-Add-in-REST-OData-BasicDataOperations
37 |
38 | Copyright (c) Microsoft Corporation
39 | All rights reserved.
40 |
41 | MIT License:
42 | Permission is hereby granted, free of charge, to any person obtaining
43 | a copy of this software and associated documentation files (the
44 | "Software"), to deal in the Software without restriction, including
45 | without limitation the rights to use, copy, modify, merge, publish,
46 | distribute, sublicense, and/or sell copies of the Software, and to
47 | permit persons to whom the Software is furnished to do so, subject to
48 | the following conditions:
49 |
50 | The above copyright notice and this permission notice shall be
51 | included in all copies or substantial portions of the Software.
52 |
53 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
54 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
55 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
56 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
57 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
58 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
59 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
60 |
61 | --%>
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb/Pages/Default.aspx.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file.
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Net;
8 | using System.Web;
9 | using System.Web.UI;
10 | using System.Web.UI.WebControls;
11 | using System.Xml;
12 |
13 | namespace SharePoint_Add_in_REST_OData_BasicDataOperationsWeb
14 | {
15 | public partial class Default : System.Web.UI.Page
16 | {
17 | SharePointContextToken contextToken;
18 | string accessToken;
19 | Uri sharepointUrl;
20 |
21 | //Create a namespace manager for parsing the ATOM XML returned by the queries.
22 | XmlNamespaceManager xmlnspm = new XmlNamespaceManager(new NameTable());
23 |
24 | protected void Page_PreInit(object sender, EventArgs e)
25 | {
26 | Uri redirectUrl;
27 | switch (SharePointContextProvider.CheckRedirectionStatus(Context, out redirectUrl))
28 | {
29 | case RedirectionStatus.Ok:
30 | return;
31 | case RedirectionStatus.ShouldRedirect:
32 | Response.Redirect(redirectUrl.AbsoluteUri, endResponse: true);
33 | break;
34 | case RedirectionStatus.CanNotRedirect:
35 | Response.Write("An error occurred while processing your request.");
36 | Response.End();
37 | break;
38 | }
39 | }
40 |
41 | // The Page_load method gets the context token and the access token. The access token is used by all of the data retrieval methods.
42 | protected void Page_Load(object sender, EventArgs e)
43 | {
44 | string contextTokenString = TokenHelper.GetContextTokenFromRequest(Request);
45 |
46 | if (contextTokenString != null)
47 | {
48 | contextToken =
49 | TokenHelper.ReadAndValidateContextToken(contextTokenString, Request.Url.Authority);
50 |
51 | sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]);
52 | accessToken =
53 | TokenHelper.GetAccessToken(contextToken, sharepointUrl.Authority).AccessToken;
54 |
55 | // In a production add-in, you should cache the access token somewhere, such as in a database
56 | // or ASP.NET Session Cache. (Do not put it in a cookie.) Your code should also check to see
57 | // if it is expired before using it (and use the refresh token to get a new one when needed).
58 | // For more information, see the MSDN topic at https://msdn.microsoft.com/library/office/dn762763.aspx
59 | // For simplicity, this sample does not follow these practices.
60 | AddListButton.CommandArgument = accessToken;
61 | RefreshListButton.CommandArgument = accessToken;
62 | RetrieveListButton.CommandArgument = accessToken;
63 | AddItemButton.CommandArgument = accessToken;
64 | DeleteListButton.CommandArgument = accessToken;
65 | ChangeListTitleButton.CommandArgument = accessToken;
66 | RetrieveLists(accessToken);
67 |
68 | }
69 | else if (!IsPostBack)
70 | {
71 | Response.Write("Could not find a context token.");
72 | }
73 | }
74 |
75 |
76 | private void RetrieveLists(string accessToken)
77 | {
78 | if (IsPostBack)
79 | {
80 | sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]);
81 | }
82 |
83 | AddItemButton.Visible = false;
84 | AddListItemBox.Visible = false;
85 | DeleteListButton.Visible = false;
86 | ChangeListTitleButton.Visible = false;
87 | ChangeListTitleBox.Visible = false;
88 | RetrieveListNameBox.Enabled = true;
89 | ListTable.Rows[0].Cells[1].Text = "List ID";
90 |
91 | //Add needed namespaces to the namespace manager.
92 | xmlnspm.AddNamespace("atom", "http://www.w3.org/2005/Atom");
93 | xmlnspm.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
94 | xmlnspm.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");
95 |
96 | //Execute a REST request for all of the site's lists.
97 | HttpWebRequest listRequest =
98 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists");
99 | listRequest.Method = "GET";
100 | listRequest.Accept = "application/atom+xml";
101 | listRequest.ContentType = "application/atom+xml;type=entry";
102 | listRequest.Headers.Add("Authorization", "Bearer " + accessToken);
103 | HttpWebResponse listResponse = (HttpWebResponse)listRequest.GetResponse();
104 | StreamReader listReader = new StreamReader(listResponse.GetResponseStream());
105 | var listXml = new XmlDocument();
106 | listXml.LoadXml(listReader.ReadToEnd());
107 |
108 | var titleList = listXml.SelectNodes("//atom:entry/atom:content/m:properties/d:Title", xmlnspm);
109 | var idList = listXml.SelectNodes("//atom:entry/atom:content/m:properties/d:Id", xmlnspm);
110 |
111 | int listCounter = 0;
112 | foreach (XmlNode title in titleList)
113 | {
114 | TableRow tableRow = new TableRow();
115 | LiteralControl idClick = new LiteralControl();
116 | //Use Javascript to populate the RetrieveListNameBox control with the list id.
117 | string clickScript = "" + idList[listCounter].InnerXml + "";
118 | idClick.Text = clickScript;
119 | TableCell tableCell1 = new TableCell();
120 | tableCell1.Controls.Add(new LiteralControl(title.InnerXml));
121 | TableCell tableCell2 = new TableCell();
122 | tableCell2.Controls.Add(idClick);
123 | tableRow.Cells.Add(tableCell1);
124 | tableRow.Cells.Add(tableCell2);
125 | ListTable.Rows.Add(tableRow);
126 | listCounter++;
127 | }
128 | }
129 |
130 | private void RetrieveListItems(string accessToken, Guid listId)
131 | {
132 | if (IsPostBack)
133 | {
134 | sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]);
135 | }
136 |
137 | //Adjust the visibility of controls on the page in light of the list-specific context.
138 | AddItemButton.Visible = true;
139 | AddListItemBox.Visible = true;
140 | DeleteListButton.Visible = true;
141 | ChangeListTitleButton.Visible = true;
142 | ChangeListTitleBox.Visible = true;
143 | RetrieveListNameBox.Enabled = false;
144 | ListTable.Rows[0].Cells[1].Text = "List Items";
145 |
146 | //Add needed namespaces to the namespace manager.
147 | xmlnspm.AddNamespace("atom", "http://www.w3.org/2005/Atom");
148 | xmlnspm.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
149 | xmlnspm.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");
150 |
151 | //Execute a REST request to get the list name.
152 | HttpWebRequest listRequest =
153 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists(guid'" + listId + "')");
154 | listRequest.Method = "GET";
155 | listRequest.Accept = "application/atom+xml";
156 | listRequest.ContentType = "application/atom+xml;type=entry";
157 | listRequest.Headers.Add("Authorization", "Bearer " + accessToken);
158 | HttpWebResponse listResponse = (HttpWebResponse)listRequest.GetResponse();
159 | StreamReader listReader = new StreamReader(listResponse.GetResponseStream());
160 | var listXml = new XmlDocument();
161 | listXml.LoadXml(listReader.ReadToEnd());
162 |
163 | var listNameNode = listXml.SelectSingleNode("//atom:entry/atom:content/m:properties/d:Title", xmlnspm);
164 | string listName = listNameNode.InnerXml;
165 |
166 | //Execute a REST request to get all of the list's items.
167 | HttpWebRequest itemRequest =
168 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists(guid'" + listId + "')/Items");
169 | itemRequest.Method = "GET";
170 | itemRequest.Accept = "application/atom+xml";
171 | itemRequest.ContentType = "application/atom+xml;type=entry";
172 | itemRequest.Headers.Add("Authorization", "Bearer " + accessToken);
173 | HttpWebResponse itemResponse = (HttpWebResponse)itemRequest.GetResponse();
174 | StreamReader itemReader = new StreamReader(itemResponse.GetResponseStream());
175 | var itemXml = new XmlDocument();
176 | itemXml.LoadXml(itemReader.ReadToEnd());
177 |
178 | var itemList = itemXml.SelectNodes("//atom:entry/atom:content/m:properties/d:Title", xmlnspm);
179 |
180 | TableRow tableRow = new TableRow();
181 | TableCell tableCell1 = new TableCell();
182 | tableCell1.Controls.Add(new LiteralControl(listName));
183 | TableCell tableCell2 = new TableCell();
184 |
185 | foreach (XmlNode itemTitle in itemList)
186 | {
187 | tableCell2.Text += itemTitle.InnerXml + "
";
188 | }
189 |
190 | tableRow.Cells.Add(tableCell1);
191 | tableRow.Cells.Add(tableCell2);
192 | ListTable.Rows.Add(tableRow);
193 | }
194 |
195 | private void AddList(string accessToken, string newListName)
196 | {
197 | if (IsPostBack)
198 | {
199 | sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]);
200 | }
201 |
202 | try
203 | {
204 |
205 |
206 | //Add pertinent namespace to the namespace manager.
207 | xmlnspm.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
208 |
209 | //Execute a REST request to get the form digest. All POST requests that change the state of resources on the host
210 | //Web require the form digest in the request header.
211 | HttpWebRequest contextinfoRequest =
212 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/contextinfo");
213 | contextinfoRequest.Method = "POST";
214 | contextinfoRequest.ContentType = "text/xml;charset=utf-8";
215 | contextinfoRequest.ContentLength = 0;
216 | contextinfoRequest.Headers.Add("Authorization", "Bearer " + accessToken);
217 |
218 | HttpWebResponse contextinfoResponse = (HttpWebResponse)contextinfoRequest.GetResponse();
219 | StreamReader contextinfoReader = new StreamReader(contextinfoResponse.GetResponseStream(), System.Text.Encoding.UTF8);
220 | var formDigestXML = new XmlDocument();
221 | formDigestXML.LoadXml(contextinfoReader.ReadToEnd());
222 | var formDigestNode = formDigestXML.SelectSingleNode("//d:FormDigestValue", xmlnspm);
223 | string formDigest = formDigestNode.InnerXml;
224 |
225 | //Execute a REST request to add a list that has the user-supplied name.
226 | //The body of the REST request is ASCII encoded and inserted into the request stream.
227 | string listPostBody = "{'__metadata':{'type':'SP.List'}, 'Title':'" + newListName + "', 'BaseTemplate': 100}";
228 | byte[] listPostData = System.Text.Encoding.ASCII.GetBytes(listPostBody);
229 |
230 | HttpWebRequest listRequest =
231 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/lists");
232 | listRequest.Method = "POST";
233 | listRequest.ContentLength = listPostBody.Length;
234 | listRequest.ContentType = "application/json;odata=verbose";
235 | listRequest.Accept = "application/json;odata=verbose";
236 | listRequest.Headers.Add("Authorization", "Bearer " + accessToken);
237 | listRequest.Headers.Add("X-RequestDigest", formDigest);
238 | Stream listRequestStream = listRequest.GetRequestStream();
239 | listRequestStream.Write(listPostData, 0, listPostData.Length);
240 | listRequestStream.Close();
241 | HttpWebResponse listResponse = (HttpWebResponse)listRequest.GetResponse();
242 |
243 | RetrieveLists(accessToken);
244 | }
245 | catch (Exception e)
246 | {
247 | AddListNameBox.Text = e.Message;
248 | }
249 | }
250 |
251 | private void AddListItem(string accessToken, Guid listId, string newItemName)
252 | {
253 | if (IsPostBack)
254 | {
255 | sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]);
256 | }
257 |
258 | try
259 | {
260 |
261 | //Add pertinent namespaces to the namespace manager.
262 | xmlnspm.AddNamespace("atom", "http://www.w3.org/2005/Atom");
263 | xmlnspm.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
264 | xmlnspm.AddNamespace("m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");
265 |
266 |
267 | //Execute a REST request to get the form digest. All POST requests that change the state of resources on the host
268 | //Web require the form digest in the request header.
269 | HttpWebRequest contextinfoRequest =
270 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/contextinfo");
271 | contextinfoRequest.Method = "POST";
272 | contextinfoRequest.ContentType = "text/xml;charset=utf-8";
273 | contextinfoRequest.ContentLength = 0;
274 | contextinfoRequest.Headers.Add("Authorization", "Bearer " + accessToken);
275 |
276 | HttpWebResponse contextinfoResponse = (HttpWebResponse)contextinfoRequest.GetResponse();
277 | StreamReader contextinfoReader = new StreamReader(contextinfoResponse.GetResponseStream(), System.Text.Encoding.UTF8);
278 | var formDigestXML = new XmlDocument();
279 | formDigestXML.LoadXml(contextinfoReader.ReadToEnd());
280 | var formDigestNode = formDigestXML.SelectSingleNode("//d:FormDigestValue", xmlnspm);
281 | string formDigest = formDigestNode.InnerXml;
282 |
283 | //Execute a REST request to get the list name and the entity type name for the list.
284 | HttpWebRequest listRequest =
285 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists(guid'" + listId + "')");
286 | listRequest.Method = "GET";
287 | listRequest.Accept = "application/atom+xml";
288 | listRequest.ContentType = "application/atom+xml;type=entry";
289 | listRequest.Headers.Add("Authorization", "Bearer " + accessToken);
290 | HttpWebResponse listResponse = (HttpWebResponse)listRequest.GetResponse();
291 | StreamReader listReader = new StreamReader(listResponse.GetResponseStream());
292 | var listXml = new XmlDocument();
293 | listXml.LoadXml(listReader.ReadToEnd());
294 |
295 | //The entity type name is the required type when you construct a request to add a list item.
296 | var entityTypeNode = listXml.SelectSingleNode("//atom:entry/atom:content/m:properties/d:ListItemEntityTypeFullName", xmlnspm);
297 | var listNameNode = listXml.SelectSingleNode("//atom:entry/atom:content/m:properties/d:Title", xmlnspm);
298 | string entityTypeName = entityTypeNode.InnerXml;
299 | string listName = listNameNode.InnerXml;
300 |
301 | //Execute a REST request to add an item to the list.
302 | string itemPostBody = "{'__metadata':{'type':'" + entityTypeName + "'}, 'Title':'" + newItemName + "'}";
303 | Byte[] itemPostData = System.Text.Encoding.ASCII.GetBytes(itemPostBody);
304 |
305 | HttpWebRequest itemRequest =
306 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists(guid'" + listId + "')/Items");
307 | itemRequest.Method = "POST";
308 | itemRequest.ContentLength = itemPostBody.Length;
309 | itemRequest.ContentType = "application/json;odata=verbose";
310 | itemRequest.Accept = "application/json;odata=verbose";
311 | itemRequest.Headers.Add("Authorization", "Bearer " + accessToken);
312 | itemRequest.Headers.Add("X-RequestDigest", formDigest);
313 | Stream itemRequestStream = itemRequest.GetRequestStream();
314 |
315 | itemRequestStream.Write(itemPostData, 0, itemPostData.Length);
316 | itemRequestStream.Close();
317 |
318 | HttpWebResponse itemResponse = (HttpWebResponse)itemRequest.GetResponse();
319 | RetrieveListItems(accessToken, listId);
320 | }
321 | catch (Exception e)
322 | {
323 | AddListItemBox.Text = e.Message;
324 | }
325 | }
326 |
327 | private void ChangeListTitle(string accessToken, Guid listId, string newListTitle)
328 | {
329 | if (IsPostBack)
330 | {
331 | sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]);
332 | }
333 |
334 | //Add pertinent namespace to the namespace manager.
335 | xmlnspm.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
336 |
337 | //Execute a REST request to get the form digest. All POST requests that change the state of resources on the host
338 | //Web require the form digest in the request header.
339 | HttpWebRequest contextinfoRequest =
340 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/contextinfo");
341 | contextinfoRequest.Method = "POST";
342 | contextinfoRequest.ContentType = "text/xml;charset=utf-8";
343 | contextinfoRequest.ContentLength = 0;
344 | contextinfoRequest.Headers.Add("Authorization", "Bearer " + accessToken);
345 |
346 | HttpWebResponse contextinfoResponse = (HttpWebResponse)contextinfoRequest.GetResponse();
347 | StreamReader contextinfoReader = new StreamReader(contextinfoResponse.GetResponseStream(), System.Text.Encoding.UTF8);
348 | var formDigestXML = new XmlDocument();
349 | formDigestXML.LoadXml(contextinfoReader.ReadToEnd());
350 | var formDigestNode = formDigestXML.SelectSingleNode("//d:FormDigestValue", xmlnspm);
351 | string formDigest = formDigestNode.InnerXml;
352 |
353 | //Execute a REST request to get the ETag value, which needs to be sent with the delete request.
354 | HttpWebRequest getListEtagRequest =
355 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists(guid'" + listId + "')");
356 | getListEtagRequest.Method = "GET";
357 | getListEtagRequest.Accept = "application/atom+xml";
358 | getListEtagRequest.ContentType = "application/atom+xml;type=entry";
359 | getListEtagRequest.Headers.Add("Authorization", "Bearer " + accessToken);
360 | HttpWebResponse listETagResponse = (HttpWebResponse)getListEtagRequest.GetResponse();
361 | string eTag = listETagResponse.Headers["ETag"];
362 |
363 | //Execute a REST request to change the list title
364 | string listPostBody = "{'__metadata':{'type':'SP.List'}, 'Title':'" + newListTitle + "'}";
365 | byte[] listPostData = System.Text.Encoding.ASCII.GetBytes(listPostBody);
366 |
367 | HttpWebRequest listRequest =
368 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/lists(guid'" + listId + "')");
369 | listRequest.Method = "POST";
370 | listRequest.ContentLength = listPostBody.Length;
371 | listRequest.ContentType = "application/json;odata=verbose";
372 | listRequest.Accept = "application/json;odata=verbose";
373 | listRequest.Headers.Add("Authorization", "Bearer " + accessToken);
374 | listRequest.Headers.Add("X-RequestDigest", formDigest);
375 | listRequest.Headers.Add("If-Match", eTag);
376 | listRequest.Headers.Add("X-Http-Method", "MERGE");
377 | Stream listRequestStream = listRequest.GetRequestStream();
378 | listRequestStream.Write(listPostData, 0, listPostData.Length);
379 | listRequestStream.Close();
380 | HttpWebResponse listResponse = (HttpWebResponse)listRequest.GetResponse();
381 |
382 | RetrieveListItems(accessToken, listId);
383 | }
384 |
385 | private void DeleteList(string accessToken, Guid listId)
386 | {
387 | if (IsPostBack)
388 | {
389 | sharepointUrl = new Uri(Request.QueryString["SPHostUrl"]);
390 | }
391 |
392 | //Add pertinent namespace to the namespace manager.
393 | xmlnspm.AddNamespace("d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
394 |
395 | //Execute a REST request to get the form digest. All POST requests that change the state of resources on the host
396 | //Web require the form digest in the request header.
397 | HttpWebRequest contextinfoRequest =
398 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/contextinfo");
399 | contextinfoRequest.Method = "POST";
400 | contextinfoRequest.ContentType = "text/xml;charset=utf-8";
401 | contextinfoRequest.ContentLength = 0;
402 | contextinfoRequest.Headers.Add("Authorization", "Bearer " + accessToken);
403 |
404 | HttpWebResponse contextinfoResponse = (HttpWebResponse)contextinfoRequest.GetResponse();
405 | StreamReader contextinfoReader = new StreamReader(contextinfoResponse.GetResponseStream(), System.Text.Encoding.UTF8);
406 | var formDigestXML = new XmlDocument();
407 | formDigestXML.LoadXml(contextinfoReader.ReadToEnd());
408 | var formDigestNode = formDigestXML.SelectSingleNode("//d:FormDigestValue", xmlnspm);
409 | string formDigest = formDigestNode.InnerXml;
410 |
411 | //Execute a REST request to get the ETag value, which needs to be sent with the delete request.
412 | HttpWebRequest getListEtagRequest =
413 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists(guid'" + listId + "')");
414 | getListEtagRequest.Method = "GET";
415 | getListEtagRequest.Accept = "application/atom+xml";
416 | getListEtagRequest.ContentType = "application/atom+xml;type=entry";
417 | getListEtagRequest.Headers.Add("Authorization", "Bearer " + accessToken);
418 | HttpWebResponse listETagResponse = (HttpWebResponse)getListEtagRequest.GetResponse();
419 | string eTag = listETagResponse.Headers["ETag"];
420 |
421 | //Execute a REST request to delete the list.
422 | HttpWebRequest deleteListRequest =
423 | (HttpWebRequest)HttpWebRequest.Create(sharepointUrl.ToString() + "/_api/Web/lists(guid'" + listId + "')");
424 | deleteListRequest.Method = "POST";
425 | deleteListRequest.ContentLength = 0;
426 | deleteListRequest.ContentType = "text/xml;charset=utf-8";
427 | deleteListRequest.Headers.Add("X-RequestDigest", formDigest);
428 | deleteListRequest.Headers.Add("If-Match", eTag);
429 | deleteListRequest.Headers.Add("Authorization", "Bearer " + accessToken);
430 | deleteListRequest.Headers.Add("X-Http-Method", "DELETE");
431 | HttpWebResponse deleteListResponse = (HttpWebResponse)deleteListRequest.GetResponse();
432 | RetrieveListNameBox.Text = "";
433 | RetrieveLists(accessToken);
434 | }
435 |
436 | protected void AddList_Click(object sender, EventArgs e)
437 | {
438 | string commandAccessToken = ((Button)sender).CommandArgument;
439 | if (AddListNameBox.Text != "")
440 | {
441 | AddList(commandAccessToken, AddListNameBox.Text);
442 | }
443 | else
444 | {
445 | AddListNameBox.Text = "Enter a list title";
446 | }
447 | }
448 |
449 | protected void RefreshList_Click(object sender, EventArgs e)
450 | {
451 | string commandAccessToken = ((Button)sender).CommandArgument;
452 | RetrieveLists(commandAccessToken);
453 | }
454 |
455 | protected void RetrieveListButton_Click(object sender, EventArgs e)
456 | {
457 | string commandAccessToken = ((Button)sender).CommandArgument;
458 | Guid listId = new Guid();
459 | if (Guid.TryParse(RetrieveListNameBox.Text, out listId))
460 | {
461 | RetrieveListItems(commandAccessToken, listId);
462 | }
463 | else
464 | {
465 | RetrieveListNameBox.Text = "Enter a List GUID";
466 | }
467 | }
468 |
469 | protected void AddItemButton_Click(object sender, EventArgs e)
470 | {
471 | string commandAccessToken = ((Button)sender).CommandArgument;
472 | Guid listId = new Guid(RetrieveListNameBox.Text);
473 | if (AddListItemBox.Text != "")
474 | {
475 | AddListItem(commandAccessToken, listId, AddListItemBox.Text);
476 | }
477 | else
478 | {
479 | AddListItemBox.Text = "Enter an item title";
480 | }
481 | }
482 |
483 | protected void DeleteListButton_Click(object sender, EventArgs e)
484 | {
485 | string commandAccessToken = ((Button)sender).CommandArgument;
486 | Guid listId = new Guid(RetrieveListNameBox.Text);
487 | DeleteList(commandAccessToken, listId);
488 | }
489 |
490 | protected void ChangeListTitleButton_Click(object sender, EventArgs e)
491 | {
492 | string commandAccessToken = ((Button)sender).CommandArgument;
493 | Guid listId = new Guid(RetrieveListNameBox.Text);
494 | if (ChangeListTitleBox.Text != "")
495 | {
496 | ChangeListTitle(commandAccessToken, listId, ChangeListTitleBox.Text);
497 | }
498 | else
499 | {
500 | ChangeListTitleBox.Text = "Enter a new list title";
501 | }
502 | }
503 | }
504 | }
505 |
506 | /*
507 | SharePoint Add-in REST/OData Basic Data Operations, https://github.com/OfficeDev/SharePoint-Add-in-REST-OData-BasicDataOperations
508 |
509 | Copyright (c) Microsoft Corporation
510 | All rights reserved.
511 |
512 | MIT License:
513 | Permission is hereby granted, free of charge, to any person obtaining
514 | a copy of this software and associated documentation files (the
515 | "Software"), to deal in the Software without restriction, including
516 | without limitation the rights to use, copy, modify, merge, publish,
517 | distribute, sublicense, and/or sell copies of the Software, and to
518 | permit persons to whom the Software is furnished to do so, subject to
519 | the following conditions:
520 |
521 | The above copyright notice and this permission notice shall be
522 | included in all copies or substantial portions of the Software.
523 |
524 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
525 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
526 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
527 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
528 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
529 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
530 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
531 |
532 | */
533 |
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb/Pages/Default.aspx.designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | //
5 | // Changes to this file may cause incorrect behavior and will be lost if
6 | // the code is regenerated.
7 | //
8 | //------------------------------------------------------------------------------
9 |
10 | namespace SharePoint_Add_in_REST_OData_BasicDataOperationsWeb {
11 |
12 |
13 | public partial class Default {
14 |
15 | ///
16 | /// form1 control.
17 | ///
18 | ///
19 | /// Auto-generated field.
20 | /// To modify move field declaration from designer file to code-behind file.
21 | ///
22 | protected global::System.Web.UI.HtmlControls.HtmlForm form1;
23 |
24 | ///
25 | /// ScriptManager1 control.
26 | ///
27 | ///
28 | /// Auto-generated field.
29 | /// To modify move field declaration from designer file to code-behind file.
30 | ///
31 | protected global::System.Web.UI.ScriptManager ScriptManager1;
32 |
33 | ///
34 | /// ListManagementPanel control.
35 | ///
36 | ///
37 | /// Auto-generated field.
38 | /// To modify move field declaration from designer file to code-behind file.
39 | ///
40 | protected global::System.Web.UI.UpdatePanel ListManagementPanel;
41 |
42 | ///
43 | /// RefreshListButton control.
44 | ///
45 | ///
46 | /// Auto-generated field.
47 | /// To modify move field declaration from designer file to code-behind file.
48 | ///
49 | protected global::System.Web.UI.WebControls.Button RefreshListButton;
50 |
51 | ///
52 | /// AddListButton control.
53 | ///
54 | ///
55 | /// Auto-generated field.
56 | /// To modify move field declaration from designer file to code-behind file.
57 | ///
58 | protected global::System.Web.UI.WebControls.Button AddListButton;
59 |
60 | ///
61 | /// AddListNameBox control.
62 | ///
63 | ///
64 | /// Auto-generated field.
65 | /// To modify move field declaration from designer file to code-behind file.
66 | ///
67 | protected global::System.Web.UI.WebControls.TextBox AddListNameBox;
68 |
69 | ///
70 | /// RetrieveListButton control.
71 | ///
72 | ///
73 | /// Auto-generated field.
74 | /// To modify move field declaration from designer file to code-behind file.
75 | ///
76 | protected global::System.Web.UI.WebControls.Button RetrieveListButton;
77 |
78 | ///
79 | /// RetrieveListNameBox control.
80 | ///
81 | ///
82 | /// Auto-generated field.
83 | /// To modify move field declaration from designer file to code-behind file.
84 | ///
85 | protected global::System.Web.UI.WebControls.TextBox RetrieveListNameBox;
86 |
87 | ///
88 | /// AddItemButton control.
89 | ///
90 | ///
91 | /// Auto-generated field.
92 | /// To modify move field declaration from designer file to code-behind file.
93 | ///
94 | protected global::System.Web.UI.WebControls.Button AddItemButton;
95 |
96 | ///
97 | /// AddListItemBox control.
98 | ///
99 | ///
100 | /// Auto-generated field.
101 | /// To modify move field declaration from designer file to code-behind file.
102 | ///
103 | protected global::System.Web.UI.WebControls.TextBox AddListItemBox;
104 |
105 | ///
106 | /// DeleteListButton control.
107 | ///
108 | ///
109 | /// Auto-generated field.
110 | /// To modify move field declaration from designer file to code-behind file.
111 | ///
112 | protected global::System.Web.UI.WebControls.Button DeleteListButton;
113 |
114 | ///
115 | /// ChangeListTitleButton control.
116 | ///
117 | ///
118 | /// Auto-generated field.
119 | /// To modify move field declaration from designer file to code-behind file.
120 | ///
121 | protected global::System.Web.UI.WebControls.Button ChangeListTitleButton;
122 |
123 | ///
124 | /// ChangeListTitleBox control.
125 | ///
126 | ///
127 | /// Auto-generated field.
128 | /// To modify move field declaration from designer file to code-behind file.
129 | ///
130 | protected global::System.Web.UI.WebControls.TextBox ChangeListTitleBox;
131 |
132 | ///
133 | /// ListTable control.
134 | ///
135 | ///
136 | /// Auto-generated field.
137 | /// To modify move field declaration from designer file to code-behind file.
138 | ///
139 | protected global::System.Web.UI.WebControls.Table ListTable;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("SharePoint_Add_in_REST_OData_BasicDataOperationsWeb")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("SharePoint_Add_in_REST_OData_BasicDataOperationsWeb")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("0e1420f0-04e8-449d-98d0-3d2ae006b95d")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Revision and Build Numbers
33 | // by using the '*' as shown below:
34 | [assembly: AssemblyVersion("1.0.0.0")]
35 | [assembly: AssemblyFileVersion("1.0.0.0")]
36 |
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 |
8 |
9 | 2.0
10 | {32170F89-0BF8-4017-9846-D2C0D81581AE}
11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}
12 | Library
13 | Properties
14 | SharePoint_Add_in_REST_OData_BasicDataOperationsWeb
15 | SharePoint-Add-in-REST-OData-BasicDataOperationsWeb
16 | v4.5
17 | true
18 | 44333
19 |
20 |
21 |
22 | 15.0
23 |
24 |
25 | true
26 | full
27 | false
28 | bin\
29 | DEBUG;TRACE
30 | prompt
31 | 4
32 |
33 |
34 | pdbonly
35 | true
36 | bin\
37 | TRACE
38 | prompt
39 | 4
40 |
41 |
42 |
43 |
44 | True
45 |
46 |
47 | True
48 |
49 |
50 | True
51 |
52 |
53 | True
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Web.config
78 |
79 |
80 | Web.config
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | Default.aspx
90 | ASPXCodeBehind
91 |
92 |
93 | Default.aspx
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | 10.0
102 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | True
112 | True
113 | 19739
114 | /
115 | http://localhost:19739/
116 | False
117 | False
118 |
119 |
120 | False
121 |
122 |
123 |
124 |
125 |
132 |
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb/SharePointContext.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file.
2 |
3 | using Microsoft.IdentityModel.S2S.Protocols.OAuth2;
4 | using Microsoft.IdentityModel.Tokens;
5 | using Microsoft.SharePoint.Client;
6 | using System;
7 | using System.Net;
8 | using System.Security.Principal;
9 | using System.Web;
10 | using System.Web.Configuration;
11 |
12 | namespace SharePoint_Add_in_REST_OData_BasicDataOperationsWeb
13 | {
14 | ///
15 | /// Encapsulates all the information from SharePoint.
16 | ///
17 | public abstract class SharePointContext
18 | {
19 | public const string SPHostUrlKey = "SPHostUrl";
20 | public const string SPAppWebUrlKey = "SPAppWebUrl";
21 | public const string SPLanguageKey = "SPLanguage";
22 | public const string SPClientTagKey = "SPClientTag";
23 | public const string SPProductNumberKey = "SPProductNumber";
24 |
25 | protected static readonly TimeSpan AccessTokenLifetimeTolerance = TimeSpan.FromMinutes(5.0);
26 |
27 | private readonly Uri spHostUrl;
28 | private readonly Uri spAppWebUrl;
29 | private readonly string spLanguage;
30 | private readonly string spClientTag;
31 | private readonly string spProductNumber;
32 |
33 | //
34 | protected Tuple userAccessTokenForSPHost;
35 | protected Tuple userAccessTokenForSPAppWeb;
36 | protected Tuple appOnlyAccessTokenForSPHost;
37 | protected Tuple appOnlyAccessTokenForSPAppWeb;
38 |
39 | ///
40 | /// Gets the SharePoint host url from QueryString of the specified HTTP request.
41 | ///
42 | /// The specified HTTP request.
43 | /// The SharePoint host url. Returns null if the HTTP request doesn't contain the SharePoint host url.
44 | public static Uri GetSPHostUrl(HttpRequestBase httpRequest)
45 | {
46 | if (httpRequest == null)
47 | {
48 | throw new ArgumentNullException("httpRequest");
49 | }
50 |
51 | string spHostUrlString = TokenHelper.EnsureTrailingSlash(httpRequest.QueryString[SPHostUrlKey]);
52 | Uri spHostUrl;
53 | if (Uri.TryCreate(spHostUrlString, UriKind.Absolute, out spHostUrl) &&
54 | (spHostUrl.Scheme == Uri.UriSchemeHttp || spHostUrl.Scheme == Uri.UriSchemeHttps))
55 | {
56 | return spHostUrl;
57 | }
58 |
59 | return null;
60 | }
61 |
62 | ///
63 | /// Gets the SharePoint host url from QueryString of the specified HTTP request.
64 | ///
65 | /// The specified HTTP request.
66 | /// The SharePoint host url. Returns null if the HTTP request doesn't contain the SharePoint host url.
67 | public static Uri GetSPHostUrl(HttpRequest httpRequest)
68 | {
69 | return GetSPHostUrl(new HttpRequestWrapper(httpRequest));
70 | }
71 |
72 | ///
73 | /// The SharePoint host url.
74 | ///
75 | public Uri SPHostUrl
76 | {
77 | get { return this.spHostUrl; }
78 | }
79 |
80 | ///
81 | /// The SharePoint app web url.
82 | ///
83 | public Uri SPAppWebUrl
84 | {
85 | get { return this.spAppWebUrl; }
86 | }
87 |
88 | ///
89 | /// The SharePoint language.
90 | ///
91 | public string SPLanguage
92 | {
93 | get { return this.spLanguage; }
94 | }
95 |
96 | ///
97 | /// The SharePoint client tag.
98 | ///
99 | public string SPClientTag
100 | {
101 | get { return this.spClientTag; }
102 | }
103 |
104 | ///
105 | /// The SharePoint product number.
106 | ///
107 | public string SPProductNumber
108 | {
109 | get { return this.spProductNumber; }
110 | }
111 |
112 | ///
113 | /// The user access token for the SharePoint host.
114 | ///
115 | public abstract string UserAccessTokenForSPHost
116 | {
117 | get;
118 | }
119 |
120 | ///
121 | /// The user access token for the SharePoint app web.
122 | ///
123 | public abstract string UserAccessTokenForSPAppWeb
124 | {
125 | get;
126 | }
127 |
128 | ///
129 | /// The app only access token for the SharePoint host.
130 | ///
131 | public abstract string AppOnlyAccessTokenForSPHost
132 | {
133 | get;
134 | }
135 |
136 | ///
137 | /// The app only access token for the SharePoint app web.
138 | ///
139 | public abstract string AppOnlyAccessTokenForSPAppWeb
140 | {
141 | get;
142 | }
143 |
144 | ///
145 | /// Constructor.
146 | ///
147 | /// The SharePoint host url.
148 | /// The SharePoint app web url.
149 | /// The SharePoint language.
150 | /// The SharePoint client tag.
151 | /// The SharePoint product number.
152 | protected SharePointContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber)
153 | {
154 | if (spHostUrl == null)
155 | {
156 | throw new ArgumentNullException("spHostUrl");
157 | }
158 |
159 | if (string.IsNullOrEmpty(spLanguage))
160 | {
161 | throw new ArgumentNullException("spLanguage");
162 | }
163 |
164 | if (string.IsNullOrEmpty(spClientTag))
165 | {
166 | throw new ArgumentNullException("spClientTag");
167 | }
168 |
169 | if (string.IsNullOrEmpty(spProductNumber))
170 | {
171 | throw new ArgumentNullException("spProductNumber");
172 | }
173 |
174 | this.spHostUrl = spHostUrl;
175 | this.spAppWebUrl = spAppWebUrl;
176 | this.spLanguage = spLanguage;
177 | this.spClientTag = spClientTag;
178 | this.spProductNumber = spProductNumber;
179 | }
180 |
181 | ///
182 | /// Creates a user ClientContext for the SharePoint host.
183 | ///
184 | /// A ClientContext instance.
185 | public ClientContext CreateUserClientContextForSPHost()
186 | {
187 | return CreateClientContext(this.SPHostUrl, this.UserAccessTokenForSPHost);
188 | }
189 |
190 | ///
191 | /// Creates a user ClientContext for the SharePoint app web.
192 | ///
193 | /// A ClientContext instance.
194 | public ClientContext CreateUserClientContextForSPAppWeb()
195 | {
196 | return CreateClientContext(this.SPAppWebUrl, this.UserAccessTokenForSPAppWeb);
197 | }
198 |
199 | ///
200 | /// Creates app only ClientContext for the SharePoint host.
201 | ///
202 | /// A ClientContext instance.
203 | public ClientContext CreateAppOnlyClientContextForSPHost()
204 | {
205 | return CreateClientContext(this.SPHostUrl, this.AppOnlyAccessTokenForSPHost);
206 | }
207 |
208 | ///
209 | /// Creates an app only ClientContext for the SharePoint app web.
210 | ///
211 | /// A ClientContext instance.
212 | public ClientContext CreateAppOnlyClientContextForSPAppWeb()
213 | {
214 | return CreateClientContext(this.SPAppWebUrl, this.AppOnlyAccessTokenForSPAppWeb);
215 | }
216 |
217 | ///
218 | /// Gets the database connection string from SharePoint for autohosted app.
219 | ///
220 | /// The database connection string. Returns null if the app is not autohosted or there is no database.
221 | public string GetDatabaseConnectionString()
222 | {
223 | string dbConnectionString = null;
224 |
225 | using (ClientContext clientContext = CreateAppOnlyClientContextForSPHost())
226 | {
227 | if (clientContext != null)
228 | {
229 | var result = AppInstance.RetrieveAppDatabaseConnectionString(clientContext);
230 |
231 | clientContext.ExecuteQuery();
232 |
233 | dbConnectionString = result.Value;
234 | }
235 | }
236 |
237 | if (dbConnectionString == null)
238 | {
239 | const string LocalDBInstanceForDebuggingKey = "LocalDBInstanceForDebugging";
240 |
241 | var dbConnectionStringSettings = WebConfigurationManager.ConnectionStrings[LocalDBInstanceForDebuggingKey];
242 |
243 | dbConnectionString = dbConnectionStringSettings != null ? dbConnectionStringSettings.ConnectionString : null;
244 | }
245 |
246 | return dbConnectionString;
247 | }
248 |
249 | ///
250 | /// Determines if the specified access token is valid.
251 | /// It considers an access token as not valid if it is null, or it has expired.
252 | ///
253 | /// The access token to verify.
254 | /// True if the access token is valid.
255 | protected static bool IsAccessTokenValid(Tuple accessToken)
256 | {
257 | return accessToken != null &&
258 | !string.IsNullOrEmpty(accessToken.Item1) &&
259 | accessToken.Item2 > DateTime.UtcNow;
260 | }
261 |
262 | ///
263 | /// Creates a ClientContext with the specified SharePoint site url and the access token.
264 | ///
265 | /// The site url.
266 | /// The access token.
267 | /// A ClientContext instance.
268 | private static ClientContext CreateClientContext(Uri spSiteUrl, string accessToken)
269 | {
270 | if (spSiteUrl != null && !string.IsNullOrEmpty(accessToken))
271 | {
272 | return TokenHelper.GetClientContextWithAccessToken(spSiteUrl.AbsoluteUri, accessToken);
273 | }
274 |
275 | return null;
276 | }
277 | }
278 |
279 | ///
280 | /// Redirection status.
281 | ///
282 | public enum RedirectionStatus
283 | {
284 | Ok,
285 | ShouldRedirect,
286 | CanNotRedirect
287 | }
288 |
289 | ///
290 | /// Provides SharePointContext instances.
291 | ///
292 | public abstract class SharePointContextProvider
293 | {
294 | private static SharePointContextProvider current;
295 |
296 | ///
297 | /// The current SharePointContextProvider instance.
298 | ///
299 | public static SharePointContextProvider Current
300 | {
301 | get { return SharePointContextProvider.current; }
302 | }
303 |
304 | ///
305 | /// Initializes the default SharePointContextProvider instance.
306 | ///
307 | static SharePointContextProvider()
308 | {
309 | if (!TokenHelper.IsHighTrustApp())
310 | {
311 | SharePointContextProvider.current = new SharePointAcsContextProvider();
312 | }
313 | else
314 | {
315 | SharePointContextProvider.current = new SharePointHighTrustContextProvider();
316 | }
317 | }
318 |
319 | ///
320 | /// Registers the specified SharePointContextProvider instance as current.
321 | /// It should be called by Application_Start() in Global.asax.
322 | ///
323 | /// The SharePointContextProvider to be set as current.
324 | public static void Register(SharePointContextProvider provider)
325 | {
326 | if (provider == null)
327 | {
328 | throw new ArgumentNullException("provider");
329 | }
330 |
331 | SharePointContextProvider.current = provider;
332 | }
333 |
334 | ///
335 | /// Checks if it is necessary to redirect to SharePoint for user to authenticate.
336 | ///
337 | /// The HTTP context.
338 | /// The redirect url to SharePoint if the status is ShouldRedirect. Null if the status is Ok or CanNotRedirect.
339 | /// Redirection status.
340 | public static RedirectionStatus CheckRedirectionStatus(HttpContextBase httpContext, out Uri redirectUrl)
341 | {
342 | if (httpContext == null)
343 | {
344 | throw new ArgumentNullException("httpContext");
345 | }
346 |
347 | redirectUrl = null;
348 |
349 | if (SharePointContextProvider.Current.GetSharePointContext(httpContext) != null)
350 | {
351 | return RedirectionStatus.Ok;
352 | }
353 |
354 | const string SPHasRedirectedToSharePointKey = "SPHasRedirectedToSharePoint";
355 |
356 | if (!string.IsNullOrEmpty(httpContext.Request.QueryString[SPHasRedirectedToSharePointKey]))
357 | {
358 | return RedirectionStatus.CanNotRedirect;
359 | }
360 |
361 | Uri spHostUrl = SharePointContext.GetSPHostUrl(httpContext.Request);
362 |
363 | if (spHostUrl == null)
364 | {
365 | return RedirectionStatus.CanNotRedirect;
366 | }
367 |
368 | if (StringComparer.OrdinalIgnoreCase.Equals(httpContext.Request.HttpMethod, "POST"))
369 | {
370 | return RedirectionStatus.CanNotRedirect;
371 | }
372 |
373 | Uri requestUrl = httpContext.Request.Url;
374 |
375 | var queryNameValueCollection = HttpUtility.ParseQueryString(requestUrl.Query);
376 |
377 | // Removes the values that are included in {StandardTokens}, as {StandardTokens} will be inserted at the beginning of the query string.
378 | queryNameValueCollection.Remove(SharePointContext.SPHostUrlKey);
379 | queryNameValueCollection.Remove(SharePointContext.SPAppWebUrlKey);
380 | queryNameValueCollection.Remove(SharePointContext.SPLanguageKey);
381 | queryNameValueCollection.Remove(SharePointContext.SPClientTagKey);
382 | queryNameValueCollection.Remove(SharePointContext.SPProductNumberKey);
383 |
384 | // Adds SPHasRedirectedToSharePoint=1.
385 | queryNameValueCollection.Add(SPHasRedirectedToSharePointKey, "1");
386 |
387 | UriBuilder returnUrlBuilder = new UriBuilder(requestUrl);
388 | returnUrlBuilder.Query = queryNameValueCollection.ToString();
389 |
390 | // Inserts StandardTokens.
391 | const string StandardTokens = "{StandardTokens}";
392 | string returnUrlString = returnUrlBuilder.Uri.AbsoluteUri;
393 | returnUrlString = returnUrlString.Insert(returnUrlString.IndexOf("?") + 1, StandardTokens + "&");
394 |
395 | // Constructs redirect url.
396 | string redirectUrlString = TokenHelper.GetAppContextTokenRequestUrl(spHostUrl.AbsoluteUri, Uri.EscapeDataString(returnUrlString));
397 |
398 | redirectUrl = new Uri(redirectUrlString, UriKind.Absolute);
399 |
400 | return RedirectionStatus.ShouldRedirect;
401 | }
402 |
403 | ///
404 | /// Checks if it is necessary to redirect to SharePoint for user to authenticate.
405 | ///
406 | /// The HTTP context.
407 | /// The redirect url to SharePoint if the status is ShouldRedirect. Null if the status is Ok or CanNotRedirect.
408 | /// Redirection status.
409 | public static RedirectionStatus CheckRedirectionStatus(HttpContext httpContext, out Uri redirectUrl)
410 | {
411 | return CheckRedirectionStatus(new HttpContextWrapper(httpContext), out redirectUrl);
412 | }
413 |
414 | ///
415 | /// Creates a SharePointContext instance with the specified HTTP request.
416 | ///
417 | /// The HTTP request.
418 | /// The SharePointContext instance. Returns null if errors occur.
419 | public SharePointContext CreateSharePointContext(HttpRequestBase httpRequest)
420 | {
421 | if (httpRequest == null)
422 | {
423 | throw new ArgumentNullException("httpRequest");
424 | }
425 |
426 | // SPHostUrl
427 | Uri spHostUrl = SharePointContext.GetSPHostUrl(httpRequest);
428 | if (spHostUrl == null)
429 | {
430 | return null;
431 | }
432 |
433 | // SPAppWebUrl
434 | string spAppWebUrlString = TokenHelper.EnsureTrailingSlash(httpRequest.QueryString[SharePointContext.SPAppWebUrlKey]);
435 | Uri spAppWebUrl;
436 | if (!Uri.TryCreate(spAppWebUrlString, UriKind.Absolute, out spAppWebUrl) ||
437 | !(spAppWebUrl.Scheme == Uri.UriSchemeHttp || spAppWebUrl.Scheme == Uri.UriSchemeHttps))
438 | {
439 | spAppWebUrl = null;
440 | }
441 |
442 | // SPLanguage
443 | string spLanguage = httpRequest.QueryString[SharePointContext.SPLanguageKey];
444 | if (string.IsNullOrEmpty(spLanguage))
445 | {
446 | return null;
447 | }
448 |
449 | // SPClientTag
450 | string spClientTag = httpRequest.QueryString[SharePointContext.SPClientTagKey];
451 | if (string.IsNullOrEmpty(spClientTag))
452 | {
453 | return null;
454 | }
455 |
456 | // SPProductNumber
457 | string spProductNumber = httpRequest.QueryString[SharePointContext.SPProductNumberKey];
458 | if (string.IsNullOrEmpty(spProductNumber))
459 | {
460 | return null;
461 | }
462 |
463 | return CreateSharePointContext(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber, httpRequest);
464 | }
465 |
466 | ///
467 | /// Creates a SharePointContext instance with the specified HTTP request.
468 | ///
469 | /// The HTTP request.
470 | /// The SharePointContext instance. Returns null if errors occur.
471 | public SharePointContext CreateSharePointContext(HttpRequest httpRequest)
472 | {
473 | return CreateSharePointContext(new HttpRequestWrapper(httpRequest));
474 | }
475 |
476 | ///
477 | /// Gets a SharePointContext instance associated with the specified HTTP context.
478 | ///
479 | /// The HTTP context.
480 | /// The SharePointContext instance. Returns null if not found and a new instance can't be created.
481 | public SharePointContext GetSharePointContext(HttpContextBase httpContext)
482 | {
483 | if (httpContext == null)
484 | {
485 | throw new ArgumentNullException("httpContext");
486 | }
487 |
488 | Uri spHostUrl = SharePointContext.GetSPHostUrl(httpContext.Request);
489 | if (spHostUrl == null)
490 | {
491 | return null;
492 | }
493 |
494 | SharePointContext spContext = LoadSharePointContext(httpContext);
495 |
496 | if (spContext == null || !ValidateSharePointContext(spContext, httpContext))
497 | {
498 | spContext = CreateSharePointContext(httpContext.Request);
499 |
500 | if (spContext != null)
501 | {
502 | SaveSharePointContext(spContext, httpContext);
503 | }
504 | }
505 |
506 | return spContext;
507 | }
508 |
509 | ///
510 | /// Gets a SharePointContext instance associated with the specified HTTP context.
511 | ///
512 | /// The HTTP context.
513 | /// The SharePointContext instance. Returns null if not found and a new instance can't be created.
514 | public SharePointContext GetSharePointContext(HttpContext httpContext)
515 | {
516 | return GetSharePointContext(new HttpContextWrapper(httpContext));
517 | }
518 |
519 | ///
520 | /// Creates a SharePointContext instance.
521 | ///
522 | /// The SharePoint host url.
523 | /// The SharePoint app web url.
524 | /// The SharePoint language.
525 | /// The SharePoint client tag.
526 | /// The SharePoint product number.
527 | /// The HTTP request.
528 | /// The SharePointContext instance. Returns null if errors occur.
529 | protected abstract SharePointContext CreateSharePointContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, HttpRequestBase httpRequest);
530 |
531 | ///
532 | /// Validates if the given SharePointContext can be used with the specified HTTP context.
533 | ///
534 | /// The SharePointContext.
535 | /// The HTTP context.
536 | /// True if the given SharePointContext can be used with the specified HTTP context.
537 | protected abstract bool ValidateSharePointContext(SharePointContext spContext, HttpContextBase httpContext);
538 |
539 | ///
540 | /// Loads the SharePointContext instance associated with the specified HTTP context.
541 | ///
542 | /// The HTTP context.
543 | /// The SharePointContext instance. Returns null if not found.
544 | protected abstract SharePointContext LoadSharePointContext(HttpContextBase httpContext);
545 |
546 | ///
547 | /// Saves the specified SharePointContext instance associated with the specified HTTP context.
548 | /// null is accepted for clearing the SharePointContext instance associated with the HTTP context.
549 | ///
550 | /// The SharePointContext instance to be saved, or null.
551 | /// The HTTP context.
552 | protected abstract void SaveSharePointContext(SharePointContext spContext, HttpContextBase httpContext);
553 | }
554 |
555 | #region ACS
556 |
557 | ///
558 | /// Encapsulates all the information from SharePoint in ACS mode.
559 | ///
560 | public class SharePointAcsContext : SharePointContext
561 | {
562 | private readonly string contextToken;
563 | private readonly SharePointContextToken contextTokenObj;
564 |
565 | ///
566 | /// The context token.
567 | ///
568 | public string ContextToken
569 | {
570 | get { return this.contextTokenObj.ValidTo > DateTime.UtcNow ? this.contextToken : null; }
571 | }
572 |
573 | ///
574 | /// The context token's "CacheKey" claim.
575 | ///
576 | public string CacheKey
577 | {
578 | get { return this.contextTokenObj.ValidTo > DateTime.UtcNow ? this.contextTokenObj.CacheKey : null; }
579 | }
580 |
581 | ///
582 | /// The context token's "refreshtoken" claim.
583 | ///
584 | public string RefreshToken
585 | {
586 | get { return this.contextTokenObj.ValidTo > DateTime.UtcNow ? this.contextTokenObj.RefreshToken : null; }
587 | }
588 |
589 | public override string UserAccessTokenForSPHost
590 | {
591 | get
592 | {
593 | return GetAccessTokenString(ref this.userAccessTokenForSPHost,
594 | () => TokenHelper.GetAccessToken(this.contextTokenObj, this.SPHostUrl.Authority));
595 | }
596 | }
597 |
598 | public override string UserAccessTokenForSPAppWeb
599 | {
600 | get
601 | {
602 | if (this.SPAppWebUrl == null)
603 | {
604 | return null;
605 | }
606 |
607 | return GetAccessTokenString(ref this.userAccessTokenForSPAppWeb,
608 | () => TokenHelper.GetAccessToken(this.contextTokenObj, this.SPAppWebUrl.Authority));
609 | }
610 | }
611 |
612 | public override string AppOnlyAccessTokenForSPHost
613 | {
614 | get
615 | {
616 | return GetAccessTokenString(ref this.appOnlyAccessTokenForSPHost,
617 | () => TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal, this.SPHostUrl.Authority, TokenHelper.GetRealmFromTargetUrl(this.SPHostUrl)));
618 | }
619 | }
620 |
621 | public override string AppOnlyAccessTokenForSPAppWeb
622 | {
623 | get
624 | {
625 | if (this.SPAppWebUrl == null)
626 | {
627 | return null;
628 | }
629 |
630 | return GetAccessTokenString(ref this.appOnlyAccessTokenForSPAppWeb,
631 | () => TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal, this.SPAppWebUrl.Authority, TokenHelper.GetRealmFromTargetUrl(this.SPAppWebUrl)));
632 | }
633 | }
634 |
635 | public SharePointAcsContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, string contextToken, SharePointContextToken contextTokenObj)
636 | : base(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber)
637 | {
638 | if (string.IsNullOrEmpty(contextToken))
639 | {
640 | throw new ArgumentNullException("contextToken");
641 | }
642 |
643 | if (contextTokenObj == null)
644 | {
645 | throw new ArgumentNullException("contextTokenObj");
646 | }
647 |
648 | this.contextToken = contextToken;
649 | this.contextTokenObj = contextTokenObj;
650 | }
651 |
652 | ///
653 | /// Ensures the access token is valid and returns it.
654 | ///
655 | /// The access token to verify.
656 | /// The token renewal handler.
657 | /// The access token string.
658 | private static string GetAccessTokenString(ref Tuple accessToken, Func tokenRenewalHandler)
659 | {
660 | RenewAccessTokenIfNeeded(ref accessToken, tokenRenewalHandler);
661 |
662 | return IsAccessTokenValid(accessToken) ? accessToken.Item1 : null;
663 | }
664 |
665 | ///
666 | /// Renews the access token if it is not valid.
667 | ///
668 | /// The access token to renew.
669 | /// The token renewal handler.
670 | private static void RenewAccessTokenIfNeeded(ref Tuple accessToken, Func tokenRenewalHandler)
671 | {
672 | if (IsAccessTokenValid(accessToken))
673 | {
674 | return;
675 | }
676 |
677 | try
678 | {
679 | OAuth2AccessTokenResponse oAuth2AccessTokenResponse = tokenRenewalHandler();
680 |
681 | DateTime expiresOn = oAuth2AccessTokenResponse.ExpiresOn;
682 |
683 | if ((expiresOn - oAuth2AccessTokenResponse.NotBefore) > AccessTokenLifetimeTolerance)
684 | {
685 | // Make the access token get renewed a bit earlier than the time when it expires
686 | // so that the calls to SharePoint with it will have enough time to complete successfully.
687 | expiresOn -= AccessTokenLifetimeTolerance;
688 | }
689 |
690 | accessToken = Tuple.Create(oAuth2AccessTokenResponse.AccessToken, expiresOn);
691 | }
692 | catch (WebException)
693 | {
694 | }
695 | }
696 | }
697 |
698 | ///
699 | /// Default provider for SharePointAcsContext.
700 | ///
701 | public class SharePointAcsContextProvider : SharePointContextProvider
702 | {
703 | private const string SPContextKey = "SPContext";
704 | private const string SPCacheKeyKey = "SPCacheKey";
705 |
706 | protected override SharePointContext CreateSharePointContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, HttpRequestBase httpRequest)
707 | {
708 | string contextTokenString = TokenHelper.GetContextTokenFromRequest(httpRequest);
709 | if (string.IsNullOrEmpty(contextTokenString))
710 | {
711 | return null;
712 | }
713 |
714 | SharePointContextToken contextToken = null;
715 | try
716 | {
717 | contextToken = TokenHelper.ReadAndValidateContextToken(contextTokenString, httpRequest.Url.Authority);
718 | }
719 | catch (WebException)
720 | {
721 | return null;
722 | }
723 | catch (AudienceUriValidationFailedException)
724 | {
725 | return null;
726 | }
727 |
728 | return new SharePointAcsContext(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber, contextTokenString, contextToken);
729 | }
730 |
731 | protected override bool ValidateSharePointContext(SharePointContext spContext, HttpContextBase httpContext)
732 | {
733 | SharePointAcsContext spAcsContext = spContext as SharePointAcsContext;
734 |
735 | if (spAcsContext != null)
736 | {
737 | Uri spHostUrl = SharePointContext.GetSPHostUrl(httpContext.Request);
738 | string contextToken = TokenHelper.GetContextTokenFromRequest(httpContext.Request);
739 | HttpCookie spCacheKeyCookie = httpContext.Request.Cookies[SPCacheKeyKey];
740 | string spCacheKey = spCacheKeyCookie != null ? spCacheKeyCookie.Value : null;
741 |
742 | return spHostUrl == spAcsContext.SPHostUrl &&
743 | !string.IsNullOrEmpty(spAcsContext.CacheKey) &&
744 | spCacheKey == spAcsContext.CacheKey &&
745 | !string.IsNullOrEmpty(spAcsContext.ContextToken) &&
746 | (string.IsNullOrEmpty(contextToken) || contextToken == spAcsContext.ContextToken);
747 | }
748 |
749 | return false;
750 | }
751 |
752 | protected override SharePointContext LoadSharePointContext(HttpContextBase httpContext)
753 | {
754 | return httpContext.Session[SPContextKey] as SharePointAcsContext;
755 | }
756 |
757 | protected override void SaveSharePointContext(SharePointContext spContext, HttpContextBase httpContext)
758 | {
759 | SharePointAcsContext spAcsContext = spContext as SharePointAcsContext;
760 |
761 | if (spAcsContext != null)
762 | {
763 | HttpCookie spCacheKeyCookie = new HttpCookie(SPCacheKeyKey)
764 | {
765 | Value = spAcsContext.CacheKey,
766 | Secure = true,
767 | HttpOnly = true
768 | };
769 |
770 | httpContext.Response.AppendCookie(spCacheKeyCookie);
771 | }
772 |
773 | httpContext.Session[SPContextKey] = spAcsContext;
774 | }
775 | }
776 |
777 | #endregion ACS
778 |
779 | #region HighTrust
780 |
781 | ///
782 | /// Encapsulates all the information from SharePoint in HighTrust mode.
783 | ///
784 | public class SharePointHighTrustContext : SharePointContext
785 | {
786 | private readonly WindowsIdentity logonUserIdentity;
787 |
788 | ///
789 | /// The Windows identity for the current user.
790 | ///
791 | public WindowsIdentity LogonUserIdentity
792 | {
793 | get { return this.logonUserIdentity; }
794 | }
795 |
796 | public override string UserAccessTokenForSPHost
797 | {
798 | get
799 | {
800 | return GetAccessTokenString(ref this.userAccessTokenForSPHost,
801 | () => TokenHelper.GetS2SAccessTokenWithWindowsIdentity(this.SPHostUrl, this.LogonUserIdentity));
802 | }
803 | }
804 |
805 | public override string UserAccessTokenForSPAppWeb
806 | {
807 | get
808 | {
809 | if (this.SPAppWebUrl == null)
810 | {
811 | return null;
812 | }
813 |
814 | return GetAccessTokenString(ref this.userAccessTokenForSPAppWeb,
815 | () => TokenHelper.GetS2SAccessTokenWithWindowsIdentity(this.SPAppWebUrl, this.LogonUserIdentity));
816 | }
817 | }
818 |
819 | public override string AppOnlyAccessTokenForSPHost
820 | {
821 | get
822 | {
823 | return GetAccessTokenString(ref this.appOnlyAccessTokenForSPHost,
824 | () => TokenHelper.GetS2SAccessTokenWithWindowsIdentity(this.SPHostUrl, null));
825 | }
826 | }
827 |
828 | public override string AppOnlyAccessTokenForSPAppWeb
829 | {
830 | get
831 | {
832 | if (this.SPAppWebUrl == null)
833 | {
834 | return null;
835 | }
836 |
837 | return GetAccessTokenString(ref this.appOnlyAccessTokenForSPAppWeb,
838 | () => TokenHelper.GetS2SAccessTokenWithWindowsIdentity(this.SPAppWebUrl, null));
839 | }
840 | }
841 |
842 | public SharePointHighTrustContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, WindowsIdentity logonUserIdentity)
843 | : base(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber)
844 | {
845 | if (logonUserIdentity == null)
846 | {
847 | throw new ArgumentNullException("logonUserIdentity");
848 | }
849 |
850 | this.logonUserIdentity = logonUserIdentity;
851 | }
852 |
853 | ///
854 | /// Ensures the access token is valid and returns it.
855 | ///
856 | /// The access token to verify.
857 | /// The token renewal handler.
858 | /// The access token string.
859 | private static string GetAccessTokenString(ref Tuple accessToken, Func tokenRenewalHandler)
860 | {
861 | RenewAccessTokenIfNeeded(ref accessToken, tokenRenewalHandler);
862 |
863 | return IsAccessTokenValid(accessToken) ? accessToken.Item1 : null;
864 | }
865 |
866 | ///
867 | /// Renews the access token if it is not valid.
868 | ///
869 | /// The access token to renew.
870 | /// The token renewal handler.
871 | private static void RenewAccessTokenIfNeeded(ref Tuple accessToken, Func tokenRenewalHandler)
872 | {
873 | if (IsAccessTokenValid(accessToken))
874 | {
875 | return;
876 | }
877 |
878 | DateTime expiresOn = DateTime.UtcNow.Add(TokenHelper.HighTrustAccessTokenLifetime);
879 |
880 | if (TokenHelper.HighTrustAccessTokenLifetime > AccessTokenLifetimeTolerance)
881 | {
882 | // Make the access token get renewed a bit earlier than the time when it expires
883 | // so that the calls to SharePoint with it will have enough time to complete successfully.
884 | expiresOn -= AccessTokenLifetimeTolerance;
885 | }
886 |
887 | accessToken = Tuple.Create(tokenRenewalHandler(), expiresOn);
888 | }
889 | }
890 |
891 | ///
892 | /// Default provider for SharePointHighTrustContext.
893 | ///
894 | public class SharePointHighTrustContextProvider : SharePointContextProvider
895 | {
896 | private const string SPContextKey = "SPContext";
897 |
898 | protected override SharePointContext CreateSharePointContext(Uri spHostUrl, Uri spAppWebUrl, string spLanguage, string spClientTag, string spProductNumber, HttpRequestBase httpRequest)
899 | {
900 | WindowsIdentity logonUserIdentity = httpRequest.LogonUserIdentity;
901 | if (logonUserIdentity == null || !logonUserIdentity.IsAuthenticated || logonUserIdentity.IsGuest || logonUserIdentity.User == null)
902 | {
903 | return null;
904 | }
905 |
906 | return new SharePointHighTrustContext(spHostUrl, spAppWebUrl, spLanguage, spClientTag, spProductNumber, logonUserIdentity);
907 | }
908 |
909 | protected override bool ValidateSharePointContext(SharePointContext spContext, HttpContextBase httpContext)
910 | {
911 | SharePointHighTrustContext spHighTrustContext = spContext as SharePointHighTrustContext;
912 |
913 | if (spHighTrustContext != null)
914 | {
915 | Uri spHostUrl = SharePointContext.GetSPHostUrl(httpContext.Request);
916 | WindowsIdentity logonUserIdentity = httpContext.Request.LogonUserIdentity;
917 |
918 | return spHostUrl == spHighTrustContext.SPHostUrl &&
919 | logonUserIdentity != null &&
920 | logonUserIdentity.IsAuthenticated &&
921 | !logonUserIdentity.IsGuest &&
922 | logonUserIdentity.User == spHighTrustContext.LogonUserIdentity.User;
923 | }
924 |
925 | return false;
926 | }
927 |
928 | protected override SharePointContext LoadSharePointContext(HttpContextBase httpContext)
929 | {
930 | return httpContext.Session[SPContextKey] as SharePointHighTrustContext;
931 | }
932 |
933 | protected override void SaveSharePointContext(SharePointContext spContext, HttpContextBase httpContext)
934 | {
935 | httpContext.Session[SPContextKey] = spContext as SharePointHighTrustContext;
936 | }
937 | }
938 |
939 | #endregion HighTrust
940 | }
941 |
942 | /*
943 | SharePoint Add-in REST/OData Basic Data Operations, https://github.com/OfficeDev/SharePoint-Add-in-REST-OData-BasicDataOperations
944 |
945 | Copyright (c) Microsoft Corporation
946 | All rights reserved.
947 |
948 | MIT License:
949 | Permission is hereby granted, free of charge, to any person obtaining
950 | a copy of this software and associated documentation files (the
951 | "Software"), to deal in the Software without restriction, including
952 | without limitation the rights to use, copy, modify, merge, publish,
953 | distribute, sublicense, and/or sell copies of the Software, and to
954 | permit persons to whom the Software is furnished to do so, subject to
955 | the following conditions:
956 |
957 | The above copyright notice and this permission notice shall be
958 | included in all copies or substantial portions of the Software.
959 |
960 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
961 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
962 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
963 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
964 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
965 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
966 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
967 |
968 | */
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb/TokenHelper.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license at the bottom of this file.
2 |
3 | using Microsoft.IdentityModel;
4 | using Microsoft.IdentityModel.S2S.Protocols.OAuth2;
5 | using Microsoft.IdentityModel.S2S.Tokens;
6 | using Microsoft.SharePoint.Client;
7 | using Microsoft.SharePoint.Client.EventReceivers;
8 | using System;
9 | using System.Collections.Generic;
10 | using System.Collections.ObjectModel;
11 | using System.Globalization;
12 | using System.IdentityModel.Selectors;
13 | using System.IdentityModel.Tokens;
14 | using System.IO;
15 | using System.Linq;
16 | using System.Net;
17 | using System.Security.Cryptography.X509Certificates;
18 | using System.Security.Principal;
19 | using System.ServiceModel;
20 | using System.Text;
21 | using System.Web;
22 | using System.Web.Configuration;
23 | using System.Web.Script.Serialization;
24 | using AudienceRestriction = Microsoft.IdentityModel.Tokens.AudienceRestriction;
25 | using AudienceUriValidationFailedException = Microsoft.IdentityModel.Tokens.AudienceUriValidationFailedException;
26 | using SecurityTokenHandlerConfiguration = Microsoft.IdentityModel.Tokens.SecurityTokenHandlerConfiguration;
27 | using X509SigningCredentials = Microsoft.IdentityModel.SecurityTokenService.X509SigningCredentials;
28 |
29 | namespace SharePoint_Add_in_REST_OData_BasicDataOperationsWeb
30 | {
31 | public static class TokenHelper
32 | {
33 | #region public fields
34 |
35 | ///
36 | /// SharePoint principal.
37 | ///
38 | public const string SharePointPrincipal = "00000003-0000-0ff1-ce00-000000000000";
39 |
40 | ///
41 | /// Lifetime of HighTrust access token, 12 hours.
42 | ///
43 | public static readonly TimeSpan HighTrustAccessTokenLifetime = TimeSpan.FromHours(12.0);
44 |
45 | #endregion public fields
46 |
47 | #region public methods
48 |
49 | ///
50 | /// Retrieves the context token string from the specified request by looking for well-known parameter names in the
51 | /// POSTed form parameters and the querystring. Returns null if no context token is found.
52 | ///
53 | /// HttpRequest in which to look for a context token
54 | /// The context token string
55 | public static string GetContextTokenFromRequest(HttpRequest request)
56 | {
57 | return GetContextTokenFromRequest(new HttpRequestWrapper(request));
58 | }
59 |
60 | ///
61 | /// Retrieves the context token string from the specified request by looking for well-known parameter names in the
62 | /// POSTed form parameters and the querystring. Returns null if no context token is found.
63 | ///
64 | /// HttpRequest in which to look for a context token
65 | /// The context token string
66 | public static string GetContextTokenFromRequest(HttpRequestBase request)
67 | {
68 | string[] paramNames = { "AppContext", "AppContextToken", "AccessToken", "SPAppToken" };
69 | foreach (string paramName in paramNames)
70 | {
71 | if (!string.IsNullOrEmpty(request.Form[paramName]))
72 | {
73 | return request.Form[paramName];
74 | }
75 | if (!string.IsNullOrEmpty(request.QueryString[paramName]))
76 | {
77 | return request.QueryString[paramName];
78 | }
79 | }
80 | return null;
81 | }
82 |
83 | ///
84 | /// Validate that a specified context token string is intended for this application based on the parameters
85 | /// specified in web.config. Parameters used from web.config used for validation include ClientId,
86 | /// HostedAppHostNameOverride, HostedAppHostName, ClientSecret, and Realm (if it is specified). If HostedAppHostNameOverride is present,
87 | /// it will be used for validation. Otherwise, if the is not
88 | /// null, it is used for validation instead of the web.config's HostedAppHostName. If the token is invalid, an
89 | /// exception is thrown. If the token is valid, TokenHelper's static STS metadata url is updated based on the token contents
90 | /// and a JsonWebSecurityToken based on the context token is returned.
91 | ///
92 | /// The context token to validate
93 | /// The URL authority, consisting of Domain Name System (DNS) host name or IP address and the port number, to use for token audience validation.
94 | /// If null, HostedAppHostName web.config setting is used instead. HostedAppHostNameOverride web.config setting, if present, will be used
95 | /// for validation instead of .
96 | /// A JsonWebSecurityToken based on the context token.
97 | public static SharePointContextToken ReadAndValidateContextToken(string contextTokenString, string appHostName = null)
98 | {
99 | JsonWebSecurityTokenHandler tokenHandler = CreateJsonWebSecurityTokenHandler();
100 | SecurityToken securityToken = tokenHandler.ReadToken(contextTokenString);
101 | JsonWebSecurityToken jsonToken = securityToken as JsonWebSecurityToken;
102 | SharePointContextToken token = SharePointContextToken.Create(jsonToken);
103 |
104 | string stsAuthority = (new Uri(token.SecurityTokenServiceUri)).Authority;
105 | int firstDot = stsAuthority.IndexOf('.');
106 |
107 | GlobalEndPointPrefix = stsAuthority.Substring(0, firstDot);
108 | AcsHostUrl = stsAuthority.Substring(firstDot + 1);
109 |
110 | tokenHandler.ValidateToken(jsonToken);
111 |
112 | string[] acceptableAudiences;
113 | if (!String.IsNullOrEmpty(HostedAppHostNameOverride))
114 | {
115 | acceptableAudiences = HostedAppHostNameOverride.Split(';');
116 | }
117 | else if (appHostName == null)
118 | {
119 | acceptableAudiences = new[] { HostedAppHostName };
120 | }
121 | else
122 | {
123 | acceptableAudiences = new[] { appHostName };
124 | }
125 |
126 | bool validationSuccessful = false;
127 | string realm = Realm ?? token.Realm;
128 | foreach (var audience in acceptableAudiences)
129 | {
130 | string principal = GetFormattedPrincipal(ClientId, audience, realm);
131 | if (StringComparer.OrdinalIgnoreCase.Equals(token.Audience, principal))
132 | {
133 | validationSuccessful = true;
134 | break;
135 | }
136 | }
137 |
138 | if (!validationSuccessful)
139 | {
140 | throw new AudienceUriValidationFailedException(
141 | String.Format(CultureInfo.CurrentCulture,
142 | "\"{0}\" is not the intended audience \"{1}\"", String.Join(";", acceptableAudiences), token.Audience));
143 | }
144 |
145 | return token;
146 | }
147 |
148 | ///
149 | /// Retrieves an access token from ACS to call the source of the specified context token at the specified
150 | /// targetHost. The targetHost must be registered for the principal that sent the context token.
151 | ///
152 | /// Context token issued by the intended access token audience
153 | /// Url authority of the target principal
154 | /// An access token with an audience matching the context token's source
155 | public static OAuth2AccessTokenResponse GetAccessToken(SharePointContextToken contextToken, string targetHost)
156 | {
157 | string targetPrincipalName = contextToken.TargetPrincipalName;
158 |
159 | // Extract the refreshToken from the context token
160 | string refreshToken = contextToken.RefreshToken;
161 |
162 | if (String.IsNullOrEmpty(refreshToken))
163 | {
164 | return null;
165 | }
166 |
167 | string targetRealm = Realm ?? contextToken.Realm;
168 |
169 | return GetAccessToken(refreshToken,
170 | targetPrincipalName,
171 | targetHost,
172 | targetRealm);
173 | }
174 |
175 | ///
176 | /// Uses the specified authorization code to retrieve an access token from ACS to call the specified principal
177 | /// at the specified targetHost. The targetHost must be registered for target principal. If specified realm is
178 | /// null, the "Realm" setting in web.config will be used instead.
179 | ///
180 | /// Authorization code to exchange for access token
181 | /// Name of the target principal to retrieve an access token for
182 | /// Url authority of the target principal
183 | /// Realm to use for the access token's nameid and audience
184 | /// Redirect URI registerd for this app
185 | /// An access token with an audience of the target principal
186 | public static OAuth2AccessTokenResponse GetAccessToken(
187 | string authorizationCode,
188 | string targetPrincipalName,
189 | string targetHost,
190 | string targetRealm,
191 | Uri redirectUri)
192 | {
193 | if (targetRealm == null)
194 | {
195 | targetRealm = Realm;
196 | }
197 |
198 | string resource = GetFormattedPrincipal(targetPrincipalName, targetHost, targetRealm);
199 | string clientId = GetFormattedPrincipal(ClientId, null, targetRealm);
200 |
201 | // Create request for token. The RedirectUri is null here. This will fail if redirect uri is registered
202 | OAuth2AccessTokenRequest oauth2Request =
203 | OAuth2MessageFactory.CreateAccessTokenRequestWithAuthorizationCode(
204 | clientId,
205 | ClientSecret,
206 | authorizationCode,
207 | redirectUri,
208 | resource);
209 |
210 | // Get token
211 | OAuth2S2SClient client = new OAuth2S2SClient();
212 | OAuth2AccessTokenResponse oauth2Response;
213 | try
214 | {
215 | oauth2Response =
216 | client.Issue(AcsMetadataParser.GetStsUrl(targetRealm), oauth2Request) as OAuth2AccessTokenResponse;
217 | }
218 | catch (WebException wex)
219 | {
220 | using (StreamReader sr = new StreamReader(wex.Response.GetResponseStream()))
221 | {
222 | string responseText = sr.ReadToEnd();
223 | throw new WebException(wex.Message + " - " + responseText, wex);
224 | }
225 | }
226 |
227 | return oauth2Response;
228 | }
229 |
230 | ///
231 | /// Uses the specified refresh token to retrieve an access token from ACS to call the specified principal
232 | /// at the specified targetHost. The targetHost must be registered for target principal. If specified realm is
233 | /// null, the "Realm" setting in web.config will be used instead.
234 | ///
235 | /// Refresh token to exchange for access token
236 | /// Name of the target principal to retrieve an access token for
237 | /// Url authority of the target principal
238 | /// Realm to use for the access token's nameid and audience
239 | /// An access token with an audience of the target principal
240 | public static OAuth2AccessTokenResponse GetAccessToken(
241 | string refreshToken,
242 | string targetPrincipalName,
243 | string targetHost,
244 | string targetRealm)
245 | {
246 | if (targetRealm == null)
247 | {
248 | targetRealm = Realm;
249 | }
250 |
251 | string resource = GetFormattedPrincipal(targetPrincipalName, targetHost, targetRealm);
252 | string clientId = GetFormattedPrincipal(ClientId, null, targetRealm);
253 |
254 | OAuth2AccessTokenRequest oauth2Request = OAuth2MessageFactory.CreateAccessTokenRequestWithRefreshToken(clientId, ClientSecret, refreshToken, resource);
255 |
256 | // Get token
257 | OAuth2S2SClient client = new OAuth2S2SClient();
258 | OAuth2AccessTokenResponse oauth2Response;
259 | try
260 | {
261 | oauth2Response =
262 | client.Issue(AcsMetadataParser.GetStsUrl(targetRealm), oauth2Request) as OAuth2AccessTokenResponse;
263 | }
264 | catch (WebException wex)
265 | {
266 | using (StreamReader sr = new StreamReader(wex.Response.GetResponseStream()))
267 | {
268 | string responseText = sr.ReadToEnd();
269 | throw new WebException(wex.Message + " - " + responseText, wex);
270 | }
271 | }
272 |
273 | return oauth2Response;
274 | }
275 |
276 | ///
277 | /// Retrieves an app-only access token from ACS to call the specified principal
278 | /// at the specified targetHost. The targetHost must be registered for target principal. If specified realm is
279 | /// null, the "Realm" setting in web.config will be used instead.
280 | ///
281 | /// Name of the target principal to retrieve an access token for
282 | /// Url authority of the target principal
283 | /// Realm to use for the access token's nameid and audience
284 | /// An access token with an audience of the target principal
285 | public static OAuth2AccessTokenResponse GetAppOnlyAccessToken(
286 | string targetPrincipalName,
287 | string targetHost,
288 | string targetRealm)
289 | {
290 |
291 | if (targetRealm == null)
292 | {
293 | targetRealm = Realm;
294 | }
295 |
296 | string resource = GetFormattedPrincipal(targetPrincipalName, targetHost, targetRealm);
297 | string clientId = GetFormattedPrincipal(ClientId, HostedAppHostName, targetRealm);
298 |
299 | OAuth2AccessTokenRequest oauth2Request = OAuth2MessageFactory.CreateAccessTokenRequestWithClientCredentials(clientId, ClientSecret, resource);
300 | oauth2Request.Resource = resource;
301 |
302 | // Get token
303 | OAuth2S2SClient client = new OAuth2S2SClient();
304 |
305 | OAuth2AccessTokenResponse oauth2Response;
306 | try
307 | {
308 | oauth2Response =
309 | client.Issue(AcsMetadataParser.GetStsUrl(targetRealm), oauth2Request) as OAuth2AccessTokenResponse;
310 | }
311 | catch (WebException wex)
312 | {
313 | using (StreamReader sr = new StreamReader(wex.Response.GetResponseStream()))
314 | {
315 | string responseText = sr.ReadToEnd();
316 | throw new WebException(wex.Message + " - " + responseText, wex);
317 | }
318 | }
319 |
320 | return oauth2Response;
321 | }
322 |
323 | ///
324 | /// Creates a client context based on the properties of a remote event receiver
325 | ///
326 | /// Properties of a remote event receiver
327 | /// A ClientContext ready to call the web where the event originated
328 | public static ClientContext CreateRemoteEventReceiverClientContext(SPRemoteEventProperties properties)
329 | {
330 | Uri sharepointUrl;
331 | if (properties.ListEventProperties != null)
332 | {
333 | sharepointUrl = new Uri(properties.ListEventProperties.WebUrl);
334 | }
335 | else if (properties.ItemEventProperties != null)
336 | {
337 | sharepointUrl = new Uri(properties.ItemEventProperties.WebUrl);
338 | }
339 | else if (properties.WebEventProperties != null)
340 | {
341 | sharepointUrl = new Uri(properties.WebEventProperties.FullUrl);
342 | }
343 | else
344 | {
345 | return null;
346 | }
347 |
348 | if (IsHighTrustApp())
349 | {
350 | return GetS2SClientContextWithWindowsIdentity(sharepointUrl, null);
351 | }
352 |
353 | return CreateAcsClientContextForUrl(properties, sharepointUrl);
354 | }
355 |
356 | ///
357 | /// Creates a client context based on the properties of an app event
358 | ///
359 | /// Properties of an app event
360 | /// True to target the app web, false to target the host web
361 | /// A ClientContext ready to call the app web or the parent web
362 | public static ClientContext CreateAppEventClientContext(SPRemoteEventProperties properties, bool useAppWeb)
363 | {
364 | if (properties.AppEventProperties == null)
365 | {
366 | return null;
367 | }
368 |
369 | Uri sharepointUrl = useAppWeb ? properties.AppEventProperties.AppWebFullUrl : properties.AppEventProperties.HostWebFullUrl;
370 | if (IsHighTrustApp())
371 | {
372 | return GetS2SClientContextWithWindowsIdentity(sharepointUrl, null);
373 | }
374 |
375 | return CreateAcsClientContextForUrl(properties, sharepointUrl);
376 | }
377 |
378 | ///
379 | /// Retrieves an access token from ACS using the specified authorization code, and uses that access token to
380 | /// create a client context
381 | ///
382 | /// Url of the target SharePoint site
383 | /// Authorization code to use when retrieving the access token from ACS
384 | /// Redirect URI registerd for this app
385 | /// A ClientContext ready to call targetUrl with a valid access token
386 | public static ClientContext GetClientContextWithAuthorizationCode(
387 | string targetUrl,
388 | string authorizationCode,
389 | Uri redirectUri)
390 | {
391 | return GetClientContextWithAuthorizationCode(targetUrl, SharePointPrincipal, authorizationCode, GetRealmFromTargetUrl(new Uri(targetUrl)), redirectUri);
392 | }
393 |
394 | ///
395 | /// Retrieves an access token from ACS using the specified authorization code, and uses that access token to
396 | /// create a client context
397 | ///
398 | /// Url of the target SharePoint site
399 | /// Name of the target SharePoint principal
400 | /// Authorization code to use when retrieving the access token from ACS
401 | /// Realm to use for the access token's nameid and audience
402 | /// Redirect URI registerd for this app
403 | /// A ClientContext ready to call targetUrl with a valid access token
404 | public static ClientContext GetClientContextWithAuthorizationCode(
405 | string targetUrl,
406 | string targetPrincipalName,
407 | string authorizationCode,
408 | string targetRealm,
409 | Uri redirectUri)
410 | {
411 | Uri targetUri = new Uri(targetUrl);
412 |
413 | string accessToken =
414 | GetAccessToken(authorizationCode, targetPrincipalName, targetUri.Authority, targetRealm, redirectUri).AccessToken;
415 |
416 | return GetClientContextWithAccessToken(targetUrl, accessToken);
417 | }
418 |
419 | ///
420 | /// Uses the specified access token to create a client context
421 | ///
422 | /// Url of the target SharePoint site
423 | /// Access token to be used when calling the specified targetUrl
424 | /// A ClientContext ready to call targetUrl with the specified access token
425 | public static ClientContext GetClientContextWithAccessToken(string targetUrl, string accessToken)
426 | {
427 | ClientContext clientContext = new ClientContext(targetUrl);
428 |
429 | clientContext.AuthenticationMode = ClientAuthenticationMode.Anonymous;
430 | clientContext.FormDigestHandlingEnabled = false;
431 | clientContext.ExecutingWebRequest +=
432 | delegate(object oSender, WebRequestEventArgs webRequestEventArgs)
433 | {
434 | webRequestEventArgs.WebRequestExecutor.RequestHeaders["Authorization"] =
435 | "Bearer " + accessToken;
436 | };
437 |
438 | return clientContext;
439 | }
440 |
441 | ///
442 | /// Retrieves an access token from ACS using the specified context token, and uses that access token to create
443 | /// a client context
444 | ///
445 | /// Url of the target SharePoint site
446 | /// Context token received from the target SharePoint site
447 | /// Url authority of the hosted app. If this is null, the value in the HostedAppHostName
448 | /// of web.config will be used instead
449 | /// A ClientContext ready to call targetUrl with a valid access token
450 | public static ClientContext GetClientContextWithContextToken(
451 | string targetUrl,
452 | string contextTokenString,
453 | string appHostUrl)
454 | {
455 | SharePointContextToken contextToken = ReadAndValidateContextToken(contextTokenString, appHostUrl);
456 |
457 | Uri targetUri = new Uri(targetUrl);
458 |
459 | string accessToken = GetAccessToken(contextToken, targetUri.Authority).AccessToken;
460 |
461 | return GetClientContextWithAccessToken(targetUrl, accessToken);
462 | }
463 |
464 | ///
465 | /// Returns the SharePoint url to which the app should redirect the browser to request consent and get back
466 | /// an authorization code.
467 | ///
468 | /// Absolute Url of the SharePoint site
469 | /// Space-delimited permissions to request from the SharePoint site in "shorthand" format
470 | /// (e.g. "Web.Read Site.Write")
471 | /// Url of the SharePoint site's OAuth authorization page
472 | public static string GetAuthorizationUrl(string contextUrl, string scope)
473 | {
474 | return string.Format(
475 | "{0}{1}?IsDlg=1&client_id={2}&scope={3}&response_type=code",
476 | EnsureTrailingSlash(contextUrl),
477 | AuthorizationPage,
478 | ClientId,
479 | scope);
480 | }
481 |
482 | ///
483 | /// Returns the SharePoint url to which the app should redirect the browser to request consent and get back
484 | /// an authorization code.
485 | ///
486 | /// Absolute Url of the SharePoint site
487 | /// Space-delimited permissions to request from the SharePoint site in "shorthand" format
488 | /// (e.g. "Web.Read Site.Write")
489 | /// Uri to which SharePoint should redirect the browser to after consent is
490 | /// granted
491 | /// Url of the SharePoint site's OAuth authorization page
492 | public static string GetAuthorizationUrl(string contextUrl, string scope, string redirectUri)
493 | {
494 | return string.Format(
495 | "{0}{1}?IsDlg=1&client_id={2}&scope={3}&response_type=code&redirect_uri={4}",
496 | EnsureTrailingSlash(contextUrl),
497 | AuthorizationPage,
498 | ClientId,
499 | scope,
500 | redirectUri);
501 | }
502 |
503 | ///
504 | /// Returns the SharePoint url to which the app should redirect the browser to request a new context token.
505 | ///
506 | /// Absolute Url of the SharePoint site
507 | /// Uri to which SharePoint should redirect the browser to with a context token
508 | /// Url of the SharePoint site's context token redirect page
509 | public static string GetAppContextTokenRequestUrl(string contextUrl, string redirectUri)
510 | {
511 | return string.Format(
512 | "{0}{1}?client_id={2}&redirect_uri={3}",
513 | EnsureTrailingSlash(contextUrl),
514 | RedirectPage,
515 | ClientId,
516 | redirectUri);
517 | }
518 |
519 | ///
520 | /// Retrieves an S2S access token signed by the application's private certificate on behalf of the specified
521 | /// WindowsIdentity and intended for the SharePoint at the targetApplicationUri. If no Realm is specified in
522 | /// web.config, an auth challenge will be issued to the targetApplicationUri to discover it.
523 | ///
524 | /// Url of the target SharePoint site
525 | /// Windows identity of the user on whose behalf to create the access token
526 | /// An access token with an audience of the target principal
527 | public static string GetS2SAccessTokenWithWindowsIdentity(
528 | Uri targetApplicationUri,
529 | WindowsIdentity identity)
530 | {
531 | string realm = string.IsNullOrEmpty(Realm) ? GetRealmFromTargetUrl(targetApplicationUri) : Realm;
532 |
533 | JsonWebTokenClaim[] claims = identity != null ? GetClaimsWithWindowsIdentity(identity) : null;
534 |
535 | return GetS2SAccessTokenWithClaims(targetApplicationUri.Authority, realm, claims);
536 | }
537 |
538 | ///
539 | /// Retrieves an S2S client context with an access token signed by the application's private certificate on
540 | /// behalf of the specified WindowsIdentity and intended for application at the targetApplicationUri using the
541 | /// targetRealm. If no Realm is specified in web.config, an auth challenge will be issued to the
542 | /// targetApplicationUri to discover it.
543 | ///
544 | /// Url of the target SharePoint site
545 | /// Windows identity of the user on whose behalf to create the access token
546 | /// A ClientContext using an access token with an audience of the target application
547 | public static ClientContext GetS2SClientContextWithWindowsIdentity(
548 | Uri targetApplicationUri,
549 | WindowsIdentity identity)
550 | {
551 | string realm = string.IsNullOrEmpty(Realm) ? GetRealmFromTargetUrl(targetApplicationUri) : Realm;
552 |
553 | JsonWebTokenClaim[] claims = identity != null ? GetClaimsWithWindowsIdentity(identity) : null;
554 |
555 | string accessToken = GetS2SAccessTokenWithClaims(targetApplicationUri.Authority, realm, claims);
556 |
557 | return GetClientContextWithAccessToken(targetApplicationUri.ToString(), accessToken);
558 | }
559 |
560 | ///
561 | /// Get authentication realm from SharePoint
562 | ///
563 | /// Url of the target SharePoint site
564 | /// String representation of the realm GUID
565 | public static string GetRealmFromTargetUrl(Uri targetApplicationUri)
566 | {
567 | WebRequest request = WebRequest.Create(targetApplicationUri + "/_vti_bin/client.svc");
568 | request.Headers.Add("Authorization: Bearer ");
569 |
570 | try
571 | {
572 | using (request.GetResponse())
573 | {
574 | }
575 | }
576 | catch (WebException e)
577 | {
578 | if (e.Response == null)
579 | {
580 | return null;
581 | }
582 |
583 | string bearerResponseHeader = e.Response.Headers["WWW-Authenticate"];
584 | if (string.IsNullOrEmpty(bearerResponseHeader))
585 | {
586 | return null;
587 | }
588 |
589 | const string bearer = "Bearer realm=\"";
590 | int bearerIndex = bearerResponseHeader.IndexOf(bearer, StringComparison.Ordinal);
591 | if (bearerIndex < 0)
592 | {
593 | return null;
594 | }
595 |
596 | int realmIndex = bearerIndex + bearer.Length;
597 |
598 | if (bearerResponseHeader.Length >= realmIndex + 36)
599 | {
600 | string targetRealm = bearerResponseHeader.Substring(realmIndex, 36);
601 |
602 | Guid realmGuid;
603 |
604 | if (Guid.TryParse(targetRealm, out realmGuid))
605 | {
606 | return targetRealm;
607 | }
608 | }
609 | }
610 | return null;
611 | }
612 |
613 | ///
614 | /// Determines if this is a high trust app.
615 | ///
616 | /// True if this is a high trust app.
617 | public static bool IsHighTrustApp()
618 | {
619 | return SigningCredentials != null;
620 | }
621 |
622 | ///
623 | /// Ensures that the specified URL ends with '/' if it is not null or empty.
624 | ///
625 | /// The url.
626 | /// The url ending with '/' if it is not null or empty.
627 | public static string EnsureTrailingSlash(string url)
628 | {
629 | if (!string.IsNullOrEmpty(url) && url[url.Length - 1] != '/')
630 | {
631 | return url + "/";
632 | }
633 |
634 | return url;
635 | }
636 |
637 | #endregion
638 |
639 | #region private fields
640 |
641 | //
642 | // Configuration Constants
643 | //
644 |
645 | private const string AuthorizationPage = "_layouts/15/OAuthAuthorize.aspx";
646 | private const string RedirectPage = "_layouts/15/AppRedirect.aspx";
647 | private const string AcsPrincipalName = "00000001-0000-0000-c000-000000000000";
648 | private const string AcsMetadataEndPointRelativeUrl = "metadata/json/1";
649 | private const string S2SProtocol = "OAuth2";
650 | private const string DelegationIssuance = "DelegationIssuance1.0";
651 | private const string NameIdentifierClaimType = JsonWebTokenConstants.ReservedClaims.NameIdentifier;
652 | private const string TrustedForImpersonationClaimType = "trustedfordelegation";
653 | private const string ActorTokenClaimType = JsonWebTokenConstants.ReservedClaims.ActorToken;
654 |
655 | //
656 | // Environment Constants
657 | //
658 |
659 | private static string GlobalEndPointPrefix = "accounts";
660 | private static string AcsHostUrl = "accesscontrol.windows.net";
661 |
662 | //
663 | // Hosted app configuration
664 | //
665 | private static readonly string ClientId = string.IsNullOrEmpty(WebConfigurationManager.AppSettings.Get("ClientId")) ? WebConfigurationManager.AppSettings.Get("HostedAppName") : WebConfigurationManager.AppSettings.Get("ClientId");
666 | private static readonly string IssuerId = string.IsNullOrEmpty(WebConfigurationManager.AppSettings.Get("IssuerId")) ? ClientId : WebConfigurationManager.AppSettings.Get("IssuerId");
667 | private static readonly string HostedAppHostNameOverride = WebConfigurationManager.AppSettings.Get("HostedAppHostNameOverride");
668 | private static readonly string HostedAppHostName = WebConfigurationManager.AppSettings.Get("HostedAppHostName");
669 | private static readonly string ClientSecret = string.IsNullOrEmpty(WebConfigurationManager.AppSettings.Get("ClientSecret")) ? WebConfigurationManager.AppSettings.Get("HostedAppSigningKey") : WebConfigurationManager.AppSettings.Get("ClientSecret");
670 | private static readonly string SecondaryClientSecret = WebConfigurationManager.AppSettings.Get("SecondaryClientSecret");
671 | private static readonly string Realm = WebConfigurationManager.AppSettings.Get("Realm");
672 | private static readonly string ServiceNamespace = WebConfigurationManager.AppSettings.Get("Realm");
673 |
674 | private static readonly string ClientSigningCertificatePath = WebConfigurationManager.AppSettings.Get("ClientSigningCertificatePath");
675 | private static readonly string ClientSigningCertificatePassword = WebConfigurationManager.AppSettings.Get("ClientSigningCertificatePassword");
676 | private static readonly X509Certificate2 ClientCertificate = (string.IsNullOrEmpty(ClientSigningCertificatePath) || string.IsNullOrEmpty(ClientSigningCertificatePassword)) ? null : new X509Certificate2(ClientSigningCertificatePath, ClientSigningCertificatePassword);
677 | private static readonly X509SigningCredentials SigningCredentials = (ClientCertificate == null) ? null : new X509SigningCredentials(ClientCertificate, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest);
678 |
679 | #endregion
680 |
681 | #region private methods
682 |
683 | private static ClientContext CreateAcsClientContextForUrl(SPRemoteEventProperties properties, Uri sharepointUrl)
684 | {
685 | string contextTokenString = properties.ContextToken;
686 |
687 | if (String.IsNullOrEmpty(contextTokenString))
688 | {
689 | return null;
690 | }
691 |
692 | SharePointContextToken contextToken = ReadAndValidateContextToken(contextTokenString, OperationContext.Current.IncomingMessageHeaders.To.Host);
693 | string accessToken = GetAccessToken(contextToken, sharepointUrl.Authority).AccessToken;
694 |
695 | return GetClientContextWithAccessToken(sharepointUrl.ToString(), accessToken);
696 | }
697 |
698 | private static string GetAcsMetadataEndpointUrl()
699 | {
700 | return Path.Combine(GetAcsGlobalEndpointUrl(), AcsMetadataEndPointRelativeUrl);
701 | }
702 |
703 | private static string GetFormattedPrincipal(string principalName, string hostName, string realm)
704 | {
705 | if (!String.IsNullOrEmpty(hostName))
706 | {
707 | return String.Format(CultureInfo.InvariantCulture, "{0}/{1}@{2}", principalName, hostName, realm);
708 | }
709 |
710 | return String.Format(CultureInfo.InvariantCulture, "{0}@{1}", principalName, realm);
711 | }
712 |
713 | private static string GetAcsPrincipalName(string realm)
714 | {
715 | return GetFormattedPrincipal(AcsPrincipalName, new Uri(GetAcsGlobalEndpointUrl()).Host, realm);
716 | }
717 |
718 | private static string GetAcsGlobalEndpointUrl()
719 | {
720 | return String.Format(CultureInfo.InvariantCulture, "https://{0}.{1}/", GlobalEndPointPrefix, AcsHostUrl);
721 | }
722 |
723 | private static JsonWebSecurityTokenHandler CreateJsonWebSecurityTokenHandler()
724 | {
725 | JsonWebSecurityTokenHandler handler = new JsonWebSecurityTokenHandler();
726 | handler.Configuration = new SecurityTokenHandlerConfiguration();
727 | handler.Configuration.AudienceRestriction = new AudienceRestriction(AudienceUriMode.Never);
728 | handler.Configuration.CertificateValidator = X509CertificateValidator.None;
729 |
730 | List securityKeys = new List();
731 | securityKeys.Add(Convert.FromBase64String(ClientSecret));
732 | if (!string.IsNullOrEmpty(SecondaryClientSecret))
733 | {
734 | securityKeys.Add(Convert.FromBase64String(SecondaryClientSecret));
735 | }
736 |
737 | List securityTokens = new List();
738 | securityTokens.Add(new MultipleSymmetricKeySecurityToken(securityKeys));
739 |
740 | handler.Configuration.IssuerTokenResolver =
741 | SecurityTokenResolver.CreateDefaultSecurityTokenResolver(
742 | new ReadOnlyCollection(securityTokens),
743 | false);
744 | SymmetricKeyIssuerNameRegistry issuerNameRegistry = new SymmetricKeyIssuerNameRegistry();
745 | foreach (byte[] securitykey in securityKeys)
746 | {
747 | issuerNameRegistry.AddTrustedIssuer(securitykey, GetAcsPrincipalName(ServiceNamespace));
748 | }
749 | handler.Configuration.IssuerNameRegistry = issuerNameRegistry;
750 | return handler;
751 | }
752 |
753 | private static string GetS2SAccessTokenWithClaims(
754 | string targetApplicationHostName,
755 | string targetRealm,
756 | IEnumerable claims)
757 | {
758 | return IssueToken(
759 | ClientId,
760 | IssuerId,
761 | targetRealm,
762 | SharePointPrincipal,
763 | targetRealm,
764 | targetApplicationHostName,
765 | true,
766 | claims,
767 | claims == null);
768 | }
769 |
770 | private static JsonWebTokenClaim[] GetClaimsWithWindowsIdentity(WindowsIdentity identity)
771 | {
772 | JsonWebTokenClaim[] claims = new JsonWebTokenClaim[]
773 | {
774 | new JsonWebTokenClaim(NameIdentifierClaimType, identity.User.Value.ToLower()),
775 | new JsonWebTokenClaim("nii", "urn:office:idp:activedirectory")
776 | };
777 | return claims;
778 | }
779 |
780 | private static string IssueToken(
781 | string sourceApplication,
782 | string issuerApplication,
783 | string sourceRealm,
784 | string targetApplication,
785 | string targetRealm,
786 | string targetApplicationHostName,
787 | bool trustedForDelegation,
788 | IEnumerable claims,
789 | bool appOnly = false)
790 | {
791 | if (null == SigningCredentials)
792 | {
793 | throw new InvalidOperationException("SigningCredentials was not initialized");
794 | }
795 |
796 | #region Actor token
797 |
798 | string issuer = string.IsNullOrEmpty(sourceRealm) ? issuerApplication : string.Format("{0}@{1}", issuerApplication, sourceRealm);
799 | string nameid = string.IsNullOrEmpty(sourceRealm) ? sourceApplication : string.Format("{0}@{1}", sourceApplication, sourceRealm);
800 | string audience = string.Format("{0}/{1}@{2}", targetApplication, targetApplicationHostName, targetRealm);
801 |
802 | List actorClaims = new List();
803 | actorClaims.Add(new JsonWebTokenClaim(JsonWebTokenConstants.ReservedClaims.NameIdentifier, nameid));
804 | if (trustedForDelegation && !appOnly)
805 | {
806 | actorClaims.Add(new JsonWebTokenClaim(TrustedForImpersonationClaimType, "true"));
807 | }
808 |
809 | // Create token
810 | JsonWebSecurityToken actorToken = new JsonWebSecurityToken(
811 | issuer: issuer,
812 | audience: audience,
813 | validFrom: DateTime.UtcNow,
814 | validTo: DateTime.UtcNow.Add(HighTrustAccessTokenLifetime),
815 | signingCredentials: SigningCredentials,
816 | claims: actorClaims);
817 |
818 | string actorTokenString = new JsonWebSecurityTokenHandler().WriteTokenAsString(actorToken);
819 |
820 | if (appOnly)
821 | {
822 | // App-only token is the same as actor token for delegated case
823 | return actorTokenString;
824 | }
825 |
826 | #endregion Actor token
827 |
828 | #region Outer token
829 |
830 | List outerClaims = null == claims ? new List() : new List(claims);
831 | outerClaims.Add(new JsonWebTokenClaim(ActorTokenClaimType, actorTokenString));
832 |
833 | JsonWebSecurityToken jsonToken = new JsonWebSecurityToken(
834 | nameid, // outer token issuer should match actor token nameid
835 | audience,
836 | DateTime.UtcNow,
837 | DateTime.UtcNow.Add(HighTrustAccessTokenLifetime),
838 | outerClaims);
839 |
840 | string accessToken = new JsonWebSecurityTokenHandler().WriteTokenAsString(jsonToken);
841 |
842 | #endregion Outer token
843 |
844 | return accessToken;
845 | }
846 |
847 | #endregion
848 |
849 | #region AcsMetadataParser
850 |
851 | // This class is used to get MetaData document from the global STS endpoint. It contains
852 | // methods to parse the MetaData document and get endpoints and STS certificate.
853 | public static class AcsMetadataParser
854 | {
855 | public static X509Certificate2 GetAcsSigningCert(string realm)
856 | {
857 | JsonMetadataDocument document = GetMetadataDocument(realm);
858 |
859 | if (null != document.keys && document.keys.Count > 0)
860 | {
861 | JsonKey signingKey = document.keys[0];
862 |
863 | if (null != signingKey && null != signingKey.keyValue)
864 | {
865 | return new X509Certificate2(Encoding.UTF8.GetBytes(signingKey.keyValue.value));
866 | }
867 | }
868 |
869 | throw new Exception("Metadata document does not contain ACS signing certificate.");
870 | }
871 |
872 | public static string GetDelegationServiceUrl(string realm)
873 | {
874 | JsonMetadataDocument document = GetMetadataDocument(realm);
875 |
876 | JsonEndpoint delegationEndpoint = document.endpoints.SingleOrDefault(e => e.protocol == DelegationIssuance);
877 |
878 | if (null != delegationEndpoint)
879 | {
880 | return delegationEndpoint.location;
881 | }
882 | throw new Exception("Metadata document does not contain Delegation Service endpoint Url");
883 | }
884 |
885 | private static JsonMetadataDocument GetMetadataDocument(string realm)
886 | {
887 | string acsMetadataEndpointUrlWithRealm = String.Format(CultureInfo.InvariantCulture, "{0}?realm={1}",
888 | GetAcsMetadataEndpointUrl(),
889 | realm);
890 | byte[] acsMetadata;
891 | using (WebClient webClient = new WebClient())
892 | {
893 |
894 | acsMetadata = webClient.DownloadData(acsMetadataEndpointUrlWithRealm);
895 | }
896 | string jsonResponseString = Encoding.UTF8.GetString(acsMetadata);
897 |
898 | JavaScriptSerializer serializer = new JavaScriptSerializer();
899 | JsonMetadataDocument document = serializer.Deserialize(jsonResponseString);
900 |
901 | if (null == document)
902 | {
903 | throw new Exception("No metadata document found at the global endpoint " + acsMetadataEndpointUrlWithRealm);
904 | }
905 |
906 | return document;
907 | }
908 |
909 | public static string GetStsUrl(string realm)
910 | {
911 | JsonMetadataDocument document = GetMetadataDocument(realm);
912 |
913 | JsonEndpoint s2sEndpoint = document.endpoints.SingleOrDefault(e => e.protocol == S2SProtocol);
914 |
915 | if (null != s2sEndpoint)
916 | {
917 | return s2sEndpoint.location;
918 | }
919 |
920 | throw new Exception("Metadata document does not contain STS endpoint url");
921 | }
922 |
923 | private class JsonMetadataDocument
924 | {
925 | public string serviceName { get; set; }
926 | public List endpoints { get; set; }
927 | public List keys { get; set; }
928 | }
929 |
930 | private class JsonEndpoint
931 | {
932 | public string location { get; set; }
933 | public string protocol { get; set; }
934 | public string usage { get; set; }
935 | }
936 |
937 | private class JsonKeyValue
938 | {
939 | public string type { get; set; }
940 | public string value { get; set; }
941 | }
942 |
943 | private class JsonKey
944 | {
945 | public string usage { get; set; }
946 | public JsonKeyValue keyValue { get; set; }
947 | }
948 | }
949 |
950 | #endregion
951 | }
952 |
953 | ///
954 | /// A JsonWebSecurityToken generated by SharePoint to authenticate to a 3rd party application and allow callbacks using a refresh token
955 | ///
956 | public class SharePointContextToken : JsonWebSecurityToken
957 | {
958 | public static SharePointContextToken Create(JsonWebSecurityToken contextToken)
959 | {
960 | return new SharePointContextToken(contextToken.Issuer, contextToken.Audience, contextToken.ValidFrom, contextToken.ValidTo, contextToken.Claims);
961 | }
962 |
963 | public SharePointContextToken(string issuer, string audience, DateTime validFrom, DateTime validTo, IEnumerable claims)
964 | : base(issuer, audience, validFrom, validTo, claims)
965 | {
966 | }
967 |
968 | public SharePointContextToken(string issuer, string audience, DateTime validFrom, DateTime validTo, IEnumerable claims, SecurityToken issuerToken, JsonWebSecurityToken actorToken)
969 | : base(issuer, audience, validFrom, validTo, claims, issuerToken, actorToken)
970 | {
971 | }
972 |
973 | public SharePointContextToken(string issuer, string audience, DateTime validFrom, DateTime validTo, IEnumerable claims, SigningCredentials signingCredentials)
974 | : base(issuer, audience, validFrom, validTo, claims, signingCredentials)
975 | {
976 | }
977 |
978 | public string NameId
979 | {
980 | get
981 | {
982 | return GetClaimValue(this, "nameid");
983 | }
984 | }
985 |
986 | ///
987 | /// The principal name portion of the context token's "appctxsender" claim
988 | ///
989 | public string TargetPrincipalName
990 | {
991 | get
992 | {
993 | string appctxsender = GetClaimValue(this, "appctxsender");
994 |
995 | if (appctxsender == null)
996 | {
997 | return null;
998 | }
999 |
1000 | return appctxsender.Split('@')[0];
1001 | }
1002 | }
1003 |
1004 | ///
1005 | /// The context token's "refreshtoken" claim
1006 | ///
1007 | public string RefreshToken
1008 | {
1009 | get
1010 | {
1011 | return GetClaimValue(this, "refreshtoken");
1012 | }
1013 | }
1014 |
1015 | ///
1016 | /// The context token's "CacheKey" claim
1017 | ///
1018 | public string CacheKey
1019 | {
1020 | get
1021 | {
1022 | string appctx = GetClaimValue(this, "appctx");
1023 | if (appctx == null)
1024 | {
1025 | return null;
1026 | }
1027 |
1028 | ClientContext ctx = new ClientContext("http://tempuri.org");
1029 | Dictionary dict = (Dictionary)ctx.ParseObjectFromJsonString(appctx);
1030 | string cacheKey = (string)dict["CacheKey"];
1031 |
1032 | return cacheKey;
1033 | }
1034 | }
1035 |
1036 | ///
1037 | /// The context token's "SecurityTokenServiceUri" claim
1038 | ///
1039 | public string SecurityTokenServiceUri
1040 | {
1041 | get
1042 | {
1043 | string appctx = GetClaimValue(this, "appctx");
1044 | if (appctx == null)
1045 | {
1046 | return null;
1047 | }
1048 |
1049 | ClientContext ctx = new ClientContext("http://tempuri.org");
1050 | Dictionary dict = (Dictionary)ctx.ParseObjectFromJsonString(appctx);
1051 | string securityTokenServiceUri = (string)dict["SecurityTokenServiceUri"];
1052 |
1053 | return securityTokenServiceUri;
1054 | }
1055 | }
1056 |
1057 | ///
1058 | /// The realm portion of the context token's "audience" claim
1059 | ///
1060 | public string Realm
1061 | {
1062 | get
1063 | {
1064 | string aud = Audience;
1065 | if (aud == null)
1066 | {
1067 | return null;
1068 | }
1069 |
1070 | string tokenRealm = aud.Substring(aud.IndexOf('@') + 1);
1071 |
1072 | return tokenRealm;
1073 | }
1074 | }
1075 |
1076 | private static string GetClaimValue(JsonWebSecurityToken token, string claimType)
1077 | {
1078 | if (token == null)
1079 | {
1080 | throw new ArgumentNullException("token");
1081 | }
1082 |
1083 | foreach (JsonWebTokenClaim claim in token.Claims)
1084 | {
1085 | if (StringComparer.Ordinal.Equals(claim.ClaimType, claimType))
1086 | {
1087 | return claim.Value;
1088 | }
1089 | }
1090 |
1091 | return null;
1092 | }
1093 |
1094 | }
1095 |
1096 | ///
1097 | /// Represents a security token which contains multiple security keys that are generated using symmetric algorithms.
1098 | ///
1099 | public class MultipleSymmetricKeySecurityToken : SecurityToken
1100 | {
1101 | ///
1102 | /// Initializes a new instance of the MultipleSymmetricKeySecurityToken class.
1103 | ///
1104 | /// An enumeration of Byte arrays that contain the symmetric keys.
1105 | public MultipleSymmetricKeySecurityToken(IEnumerable keys)
1106 | : this(UniqueId.CreateUniqueId(), keys)
1107 | {
1108 | }
1109 |
1110 | ///
1111 | /// Initializes a new instance of the MultipleSymmetricKeySecurityToken class.
1112 | ///
1113 | /// The unique identifier of the security token.
1114 | /// An enumeration of Byte arrays that contain the symmetric keys.
1115 | public MultipleSymmetricKeySecurityToken(string tokenId, IEnumerable keys)
1116 | {
1117 | if (keys == null)
1118 | {
1119 | throw new ArgumentNullException("keys");
1120 | }
1121 |
1122 | if (String.IsNullOrEmpty(tokenId))
1123 | {
1124 | throw new ArgumentException("Value cannot be a null or empty string.", "tokenId");
1125 | }
1126 |
1127 | foreach (byte[] key in keys)
1128 | {
1129 | if (key.Length <= 0)
1130 | {
1131 | throw new ArgumentException("The key length must be greater then zero.", "keys");
1132 | }
1133 | }
1134 |
1135 | id = tokenId;
1136 | effectiveTime = DateTime.UtcNow;
1137 | securityKeys = CreateSymmetricSecurityKeys(keys);
1138 | }
1139 |
1140 | ///
1141 | /// Gets the unique identifier of the security token.
1142 | ///
1143 | public override string Id
1144 | {
1145 | get
1146 | {
1147 | return id;
1148 | }
1149 | }
1150 |
1151 | ///
1152 | /// Gets the cryptographic keys associated with the security token.
1153 | ///
1154 | public override ReadOnlyCollection SecurityKeys
1155 | {
1156 | get
1157 | {
1158 | return securityKeys.AsReadOnly();
1159 | }
1160 | }
1161 |
1162 | ///
1163 | /// Gets the first instant in time at which this security token is valid.
1164 | ///
1165 | public override DateTime ValidFrom
1166 | {
1167 | get
1168 | {
1169 | return effectiveTime;
1170 | }
1171 | }
1172 |
1173 | ///
1174 | /// Gets the last instant in time at which this security token is valid.
1175 | ///
1176 | public override DateTime ValidTo
1177 | {
1178 | get
1179 | {
1180 | // Never expire
1181 | return DateTime.MaxValue;
1182 | }
1183 | }
1184 |
1185 | ///
1186 | /// Returns a value that indicates whether the key identifier for this instance can be resolved to the specified key identifier.
1187 | ///
1188 | /// A SecurityKeyIdentifierClause to compare to this instance
1189 | /// true if keyIdentifierClause is a SecurityKeyIdentifierClause and it has the same unique identifier as the Id property; otherwise, false.
1190 | public override bool MatchesKeyIdentifierClause(SecurityKeyIdentifierClause keyIdentifierClause)
1191 | {
1192 | if (keyIdentifierClause == null)
1193 | {
1194 | throw new ArgumentNullException("keyIdentifierClause");
1195 | }
1196 |
1197 | // Since this is a symmetric token and we do not have IDs to distinguish tokens, we just check for the
1198 | // presence of a SymmetricIssuerKeyIdentifier. The actual mapping to the issuer takes place later
1199 | // when the key is matched to the issuer.
1200 | if (keyIdentifierClause is SymmetricIssuerKeyIdentifierClause)
1201 | {
1202 | return true;
1203 | }
1204 | return base.MatchesKeyIdentifierClause(keyIdentifierClause);
1205 | }
1206 |
1207 | #region private members
1208 |
1209 | private List CreateSymmetricSecurityKeys(IEnumerable keys)
1210 | {
1211 | List symmetricKeys = new List();
1212 | foreach (byte[] key in keys)
1213 | {
1214 | symmetricKeys.Add(new InMemorySymmetricSecurityKey(key));
1215 | }
1216 | return symmetricKeys;
1217 | }
1218 |
1219 | private string id;
1220 | private DateTime effectiveTime;
1221 | private List securityKeys;
1222 |
1223 | #endregion
1224 | }
1225 | }
1226 |
1227 | /*
1228 | SharePoint Add-in REST/OData Basic Data Operations, https://github.com/OfficeDev/SharePoint-Add-in-REST-OData-BasicDataOperations
1229 |
1230 | Copyright (c) Microsoft Corporation
1231 | All rights reserved.
1232 |
1233 | MIT License:
1234 | Permission is hereby granted, free of charge, to any person obtaining
1235 | a copy of this software and associated documentation files (the
1236 | "Software"), to deal in the Software without restriction, including
1237 | without limitation the rights to use, copy, modify, merge, publish,
1238 | distribute, sublicense, and/or sell copies of the Software, and to
1239 | permit persons to whom the Software is furnished to do so, subject to
1240 | the following conditions:
1241 |
1242 | The above copyright notice and this permission notice shall be
1243 | included in all copies or substantial portions of the Software.
1244 |
1245 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1246 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1247 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1248 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
1249 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
1250 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1251 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1252 |
1253 | */
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb/Web.Debug.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
29 |
30 |
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb/Web.Release.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
19 |
30 |
31 |
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb/Web.config:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/SharePoint-Add-in-REST-OData-BasicDataOperationsWeb/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/description/fig1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OfficeDev/SharePoint-Add-in-REST-OData-BasicDataOperations/b700eb7c6114ffe3288f72398ea55db53dc3ea0e/description/fig1.gif
--------------------------------------------------------------------------------